blob: 4c5ba7e10d0d9d30327ecdc49e31781db5b12813 [file] [log] [blame]
xf.li86118912025-03-19 20:07:27 -07001#! /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_MAXCHALLENGE = 5 # Maximum number of AUTH challenges sent
68
69OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
70
71# Exception classes used by this module.
72class SMTPException(OSError):
73 """Base class for all exceptions raised by this module."""
74
75class SMTPNotSupportedError(SMTPException):
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
82class SMTPServerDisconnected(SMTPException):
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
90class SMTPResponseException(SMTPException):
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
104class SMTPSenderRefused(SMTPResponseException):
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
117class SMTPRecipientsRefused(SMTPException):
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
130class SMTPDataError(SMTPResponseException):
131 """The SMTP server didn't accept the data."""
132
133class SMTPConnectError(SMTPResponseException):
134 """Error during connection establishment."""
135
136class SMTPHeloError(SMTPResponseException):
137 """The server refused our HELO reply."""
138
139class SMTPAuthenticationError(SMTPResponseException):
140 """Authentication error.
141
142 Most probably the server didn't accept the username/password
143 combination provided.
144 """
145
146def 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
159def _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.
167def 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
176def _quote_periods(bindata):
177 return re.sub(br'(?m)^\.', b'..', bindata)
178
179def _fix_eols(data):
180 return re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)
181
182try:
183 import ssl
184except ImportError:
185 _have_ssl = False
186else:
187 _have_ssl = True
188
189
190class SMTP:
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 = 0
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 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 self._auth_challenge_count = 1
637 else:
638 (code, resp) = self.docmd("AUTH", mechanism)
639 self._auth_challenge_count = 0
640 # If server responds with a challenge, send the response.
641 while code == 334:
642 self._auth_challenge_count += 1
643 challenge = base64.decodebytes(resp)
644 response = encode_base64(
645 authobject(challenge).encode('ascii'), eol='')
646 (code, resp) = self.docmd(response)
647 # If server keeps sending challenges, something is wrong.
648 if self._auth_challenge_count > _MAXCHALLENGE:
649 raise SMTPException(
650 "Server AUTH mechanism infinite loop. Last response: "
651 + repr((code, resp))
652 )
653 if code in (235, 503):
654 return (code, resp)
655 raise SMTPAuthenticationError(code, resp)
656
657 def auth_cram_md5(self, challenge=None):
658 """ Authobject to use with CRAM-MD5 authentication. Requires self.user
659 and self.password to be set."""
660 # CRAM-MD5 does not support initial-response.
661 if challenge is None:
662 return None
663 return self.user + " " + hmac.HMAC(
664 self.password.encode('ascii'), challenge, 'md5').hexdigest()
665
666 def auth_plain(self, challenge=None):
667 """ Authobject to use with PLAIN authentication. Requires self.user and
668 self.password to be set."""
669 return "\0%s\0%s" % (self.user, self.password)
670
671 def auth_login(self, challenge=None):
672 """ Authobject to use with LOGIN authentication. Requires self.user and
673 self.password to be set."""
674 if challenge is None or self._auth_challenge_count < 2:
675 return self.user
676 else:
677 return self.password
678
679 def login(self, user, password, *, initial_response_ok=True):
680 """Log in on an SMTP server that requires authentication.
681
682 The arguments are:
683 - user: The user name to authenticate with.
684 - password: The password for the authentication.
685
686 Keyword arguments:
687 - initial_response_ok: Allow sending the RFC 4954 initial-response
688 to the AUTH command, if the authentication methods supports it.
689
690 If there has been no previous EHLO or HELO command this session, this
691 method tries ESMTP EHLO first.
692
693 This method will return normally if the authentication was successful.
694
695 This method may raise the following exceptions:
696
697 SMTPHeloError The server didn't reply properly to
698 the helo greeting.
699 SMTPAuthenticationError The server didn't accept the username/
700 password combination.
701 SMTPNotSupportedError The AUTH command is not supported by the
702 server.
703 SMTPException No suitable authentication method was
704 found.
705 """
706
707 self.ehlo_or_helo_if_needed()
708 if not self.has_extn("auth"):
709 raise SMTPNotSupportedError(
710 "SMTP AUTH extension not supported by server.")
711
712 # Authentication methods the server claims to support
713 advertised_authlist = self.esmtp_features["auth"].split()
714
715 # Authentication methods we can handle in our preferred order:
716 preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN']
717
718 # We try the supported authentications in our preferred order, if
719 # the server supports them.
720 authlist = [auth for auth in preferred_auths
721 if auth in advertised_authlist]
722 if not authlist:
723 raise SMTPException("No suitable authentication method found.")
724
725 # Some servers advertise authentication methods they don't really
726 # support, so if authentication fails, we continue until we've tried
727 # all methods.
728 self.user, self.password = user, password
729 for authmethod in authlist:
730 method_name = 'auth_' + authmethod.lower().replace('-', '_')
731 try:
732 (code, resp) = self.auth(
733 authmethod, getattr(self, method_name),
734 initial_response_ok=initial_response_ok)
735 # 235 == 'Authentication successful'
736 # 503 == 'Error: already authenticated'
737 if code in (235, 503):
738 return (code, resp)
739 except SMTPAuthenticationError as e:
740 last_exception = e
741
742 # We could not login successfully. Return result of last attempt.
743 raise last_exception
744
745 def starttls(self, keyfile=None, certfile=None, context=None):
746 """Puts the connection to the SMTP server into TLS mode.
747
748 If there has been no previous EHLO or HELO command this session, this
749 method tries ESMTP EHLO first.
750
751 If the server supports TLS, this will encrypt the rest of the SMTP
752 session. If you provide the keyfile and certfile parameters,
753 the identity of the SMTP server and client can be checked. This,
754 however, depends on whether the socket module really checks the
755 certificates.
756
757 This method may raise the following exceptions:
758
759 SMTPHeloError The server didn't reply properly to
760 the helo greeting.
761 """
762 self.ehlo_or_helo_if_needed()
763 if not self.has_extn("starttls"):
764 raise SMTPNotSupportedError(
765 "STARTTLS extension not supported by server.")
766 (resp, reply) = self.docmd("STARTTLS")
767 if resp == 220:
768 if not _have_ssl:
769 raise RuntimeError("No SSL support included in this Python")
770 if context is not None and keyfile is not None:
771 raise ValueError("context and keyfile arguments are mutually "
772 "exclusive")
773 if context is not None and certfile is not None:
774 raise ValueError("context and certfile arguments are mutually "
775 "exclusive")
776 if keyfile is not None or certfile is not None:
777 import warnings
778 warnings.warn("keyfile and certfile are deprecated, use a "
779 "custom context instead", DeprecationWarning, 2)
780 if context is None:
781 context = ssl._create_stdlib_context(certfile=certfile,
782 keyfile=keyfile)
783 self.sock = context.wrap_socket(self.sock,
784 server_hostname=self._host)
785 self.file = None
786 # RFC 3207:
787 # The client MUST discard any knowledge obtained from
788 # the server, such as the list of SMTP service extensions,
789 # which was not obtained from the TLS negotiation itself.
790 self.helo_resp = None
791 self.ehlo_resp = None
792 self.esmtp_features = {}
793 self.does_esmtp = 0
794 else:
795 # RFC 3207:
796 # 501 Syntax error (no parameters allowed)
797 # 454 TLS not available due to temporary reason
798 raise SMTPResponseException(resp, reply)
799 return (resp, reply)
800
801 def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
802 rcpt_options=()):
803 """This command performs an entire mail transaction.
804
805 The arguments are:
806 - from_addr : The address sending this mail.
807 - to_addrs : A list of addresses to send this mail to. A bare
808 string will be treated as a list with 1 address.
809 - msg : The message to send.
810 - mail_options : List of ESMTP options (such as 8bitmime) for the
811 mail command.
812 - rcpt_options : List of ESMTP options (such as DSN commands) for
813 all the rcpt commands.
814
815 msg may be a string containing characters in the ASCII range, or a byte
816 string. A string is encoded to bytes using the ascii codec, and lone
817 \\r and \\n characters are converted to \\r\\n characters.
818
819 If there has been no previous EHLO or HELO command this session, this
820 method tries ESMTP EHLO first. If the server does ESMTP, message size
821 and each of the specified options will be passed to it. If EHLO
822 fails, HELO will be tried and ESMTP options suppressed.
823
824 This method will return normally if the mail is accepted for at least
825 one recipient. It returns a dictionary, with one entry for each
826 recipient that was refused. Each entry contains a tuple of the SMTP
827 error code and the accompanying error message sent by the server.
828
829 This method may raise the following exceptions:
830
831 SMTPHeloError The server didn't reply properly to
832 the helo greeting.
833 SMTPRecipientsRefused The server rejected ALL recipients
834 (no mail was sent).
835 SMTPSenderRefused The server didn't accept the from_addr.
836 SMTPDataError The server replied with an unexpected
837 error code (other than a refusal of
838 a recipient).
839 SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8'
840 but the SMTPUTF8 extension is not supported by
841 the server.
842
843 Note: the connection will be open even after an exception is raised.
844
845 Example:
846
847 >>> import smtplib
848 >>> s=smtplib.SMTP("localhost")
849 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
850 >>> msg = '''\\
851 ... From: Me@my.org
852 ... Subject: testin'...
853 ...
854 ... This is a test '''
855 >>> s.sendmail("me@my.org",tolist,msg)
856 { "three@three.org" : ( 550 ,"User unknown" ) }
857 >>> s.quit()
858
859 In the above example, the message was accepted for delivery to three
860 of the four addresses, and one was rejected, with the error code
861 550. If all addresses are accepted, then the method will return an
862 empty dictionary.
863
864 """
865 self.ehlo_or_helo_if_needed()
866 esmtp_opts = []
867 if isinstance(msg, str):
868 msg = _fix_eols(msg).encode('ascii')
869 if self.does_esmtp:
870 if self.has_extn('size'):
871 esmtp_opts.append("size=%d" % len(msg))
872 for option in mail_options:
873 esmtp_opts.append(option)
874 (code, resp) = self.mail(from_addr, esmtp_opts)
875 if code != 250:
876 if code == 421:
877 self.close()
878 else:
879 self._rset()
880 raise SMTPSenderRefused(code, resp, from_addr)
881 senderrs = {}
882 if isinstance(to_addrs, str):
883 to_addrs = [to_addrs]
884 for each in to_addrs:
885 (code, resp) = self.rcpt(each, rcpt_options)
886 if (code != 250) and (code != 251):
887 senderrs[each] = (code, resp)
888 if code == 421:
889 self.close()
890 raise SMTPRecipientsRefused(senderrs)
891 if len(senderrs) == len(to_addrs):
892 # the server refused all our recipients
893 self._rset()
894 raise SMTPRecipientsRefused(senderrs)
895 (code, resp) = self.data(msg)
896 if code != 250:
897 if code == 421:
898 self.close()
899 else:
900 self._rset()
901 raise SMTPDataError(code, resp)
902 #if we got here then somebody got our mail
903 return senderrs
904
905 def send_message(self, msg, from_addr=None, to_addrs=None,
906 mail_options=(), rcpt_options=()):
907 """Converts message to a bytestring and passes it to sendmail.
908
909 The arguments are as for sendmail, except that msg is an
910 email.message.Message object. If from_addr is None or to_addrs is
911 None, these arguments are taken from the headers of the Message as
912 described in RFC 2822 (a ValueError is raised if there is more than
913 one set of 'Resent-' headers). Regardless of the values of from_addr and
914 to_addr, any Bcc field (or Resent-Bcc field, when the Message is a
915 resent) of the Message object won't be transmitted. The Message
916 object is then serialized using email.generator.BytesGenerator and
917 sendmail is called to transmit the message. If the sender or any of
918 the recipient addresses contain non-ASCII and the server advertises the
919 SMTPUTF8 capability, the policy is cloned with utf8 set to True for the
920 serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send.
921 If the server does not support SMTPUTF8, an SMTPNotSupported error is
922 raised. Otherwise the generator is called without modifying the
923 policy.
924
925 """
926 # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822
927 # Section 3.6.6). In such a case, we use the 'Resent-*' fields. However,
928 # if there is more than one 'Resent-' block there's no way to
929 # unambiguously determine which one is the most recent in all cases,
930 # so rather than guess we raise a ValueError in that case.
931 #
932 # TODO implement heuristics to guess the correct Resent-* block with an
933 # option allowing the user to enable the heuristics. (It should be
934 # possible to guess correctly almost all of the time.)
935
936 self.ehlo_or_helo_if_needed()
937 resent = msg.get_all('Resent-Date')
938 if resent is None:
939 header_prefix = ''
940 elif len(resent) == 1:
941 header_prefix = 'Resent-'
942 else:
943 raise ValueError("message has more than one 'Resent-' header block")
944 if from_addr is None:
945 # Prefer the sender field per RFC 2822:3.6.2.
946 from_addr = (msg[header_prefix + 'Sender']
947 if (header_prefix + 'Sender') in msg
948 else msg[header_prefix + 'From'])
949 from_addr = email.utils.getaddresses([from_addr])[0][1]
950 if to_addrs is None:
951 addr_fields = [f for f in (msg[header_prefix + 'To'],
952 msg[header_prefix + 'Bcc'],
953 msg[header_prefix + 'Cc'])
954 if f is not None]
955 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]
956 # Make a local copy so we can delete the bcc headers.
957 msg_copy = copy.copy(msg)
958 del msg_copy['Bcc']
959 del msg_copy['Resent-Bcc']
960 international = False
961 try:
962 ''.join([from_addr, *to_addrs]).encode('ascii')
963 except UnicodeEncodeError:
964 if not self.has_extn('smtputf8'):
965 raise SMTPNotSupportedError(
966 "One or more source or delivery addresses require"
967 " internationalized email support, but the server"
968 " does not advertise the required SMTPUTF8 capability")
969 international = True
970 with io.BytesIO() as bytesmsg:
971 if international:
972 g = email.generator.BytesGenerator(
973 bytesmsg, policy=msg.policy.clone(utf8=True))
974 mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME')
975 else:
976 g = email.generator.BytesGenerator(bytesmsg)
977 g.flatten(msg_copy, linesep='\r\n')
978 flatmsg = bytesmsg.getvalue()
979 return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,
980 rcpt_options)
981
982 def close(self):
983 """Close the connection to the SMTP server."""
984 try:
985 file = self.file
986 self.file = None
987 if file:
988 file.close()
989 finally:
990 sock = self.sock
991 self.sock = None
992 if sock:
993 sock.close()
994
995 def quit(self):
996 """Terminate the SMTP session."""
997 res = self.docmd("quit")
998 # A new EHLO is required after reconnecting with connect()
999 self.ehlo_resp = self.helo_resp = None
1000 self.esmtp_features = {}
1001 self.does_esmtp = False
1002 self.close()
1003 return res
1004
1005if _have_ssl:
1006
1007 class SMTP_SSL(SMTP):
1008 """ This is a subclass derived from SMTP that connects over an SSL
1009 encrypted socket (to use this class you need a socket module that was
1010 compiled with SSL support). If host is not specified, '' (the local
1011 host) is used. If port is omitted, the standard SMTP-over-SSL port
1012 (465) is used. local_hostname and source_address have the same meaning
1013 as they do in the SMTP class. keyfile and certfile are also optional -
1014 they can contain a PEM formatted private key and certificate chain file
1015 for the SSL connection. context also optional, can contain a
1016 SSLContext, and is an alternative to keyfile and certfile; If it is
1017 specified both keyfile and certfile must be None.
1018
1019 """
1020
1021 default_port = SMTP_SSL_PORT
1022
1023 def __init__(self, host='', port=0, local_hostname=None,
1024 keyfile=None, certfile=None,
1025 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
1026 source_address=None, context=None):
1027 if context is not None and keyfile is not None:
1028 raise ValueError("context and keyfile arguments are mutually "
1029 "exclusive")
1030 if context is not None and certfile is not None:
1031 raise ValueError("context and certfile arguments are mutually "
1032 "exclusive")
1033 if keyfile is not None or certfile is not None:
1034 import warnings
1035 warnings.warn("keyfile and certfile are deprecated, use a "
1036 "custom context instead", DeprecationWarning, 2)
1037 self.keyfile = keyfile
1038 self.certfile = certfile
1039 if context is None:
1040 context = ssl._create_stdlib_context(certfile=certfile,
1041 keyfile=keyfile)
1042 self.context = context
1043 SMTP.__init__(self, host, port, local_hostname, timeout,
1044 source_address)
1045
1046 def _get_socket(self, host, port, timeout):
1047 if self.debuglevel > 0:
1048 self._print_debug('connect:', (host, port))
1049 new_socket = socket.create_connection((host, port), timeout,
1050 self.source_address)
1051 new_socket = self.context.wrap_socket(new_socket,
1052 server_hostname=self._host)
1053 return new_socket
1054
1055 __all__.append("SMTP_SSL")
1056
1057#
1058# LMTP extension
1059#
1060LMTP_PORT = 2003
1061
1062class LMTP(SMTP):
1063 """LMTP - Local Mail Transfer Protocol
1064
1065 The LMTP protocol, which is very similar to ESMTP, is heavily based
1066 on the standard SMTP client. It's common to use Unix sockets for
1067 LMTP, so our connect() method must support that as well as a regular
1068 host:port server. local_hostname and source_address have the same
1069 meaning as they do in the SMTP class. To specify a Unix socket,
1070 you must use an absolute path as the host, starting with a '/'.
1071
1072 Authentication is supported, using the regular SMTP mechanism. When
1073 using a Unix socket, LMTP generally don't support or require any
1074 authentication, but your mileage might vary."""
1075
1076 ehlo_msg = "lhlo"
1077
1078 def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
1079 source_address=None):
1080 """Initialize a new instance."""
1081 SMTP.__init__(self, host, port, local_hostname=local_hostname,
1082 source_address=source_address)
1083
1084 def connect(self, host='localhost', port=0, source_address=None):
1085 """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
1086 if host[0] != '/':
1087 return SMTP.connect(self, host, port, source_address=source_address)
1088
1089 # Handle Unix-domain sockets.
1090 try:
1091 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1092 self.file = None
1093 self.sock.connect(host)
1094 except OSError:
1095 if self.debuglevel > 0:
1096 self._print_debug('connect fail:', host)
1097 if self.sock:
1098 self.sock.close()
1099 self.sock = None
1100 raise
1101 (code, msg) = self.getreply()
1102 if self.debuglevel > 0:
1103 self._print_debug('connect:', msg)
1104 return (code, msg)
1105
1106
1107# Test the sendmail method, which tests most of the others.
1108# Note: This always sends to localhost.
1109if __name__ == '__main__':
1110 def prompt(prompt):
1111 sys.stdout.write(prompt + ": ")
1112 sys.stdout.flush()
1113 return sys.stdin.readline().strip()
1114
1115 fromaddr = prompt("From")
1116 toaddrs = prompt("To").split(',')
1117 print("Enter message, end with ^D:")
1118 msg = ''
1119 while 1:
1120 line = sys.stdin.readline()
1121 if not line:
1122 break
1123 msg = msg + line
1124 print("Message length is %d" % len(msg))
1125
1126 server = SMTP('localhost')
1127 server.set_debuglevel(1)
1128 server.sendmail(fromaddr, toaddrs, msg)
1129 server.quit()