ยปCore Development>Code coverage>Lib/smtplib.py

Python code coverage for Lib/smtplib.py

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