Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/smtplib.py: 19%

500 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1#! /usr/bin/env python3 

2 

3'''SMTP/ESMTP client class. 

4 

5This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP 

6Authentication) and RFC 2487 (Secure SMTP over TLS). 

7 

8Notes: 

9 

10Please remember, when doing ESMTP, that the names of the SMTP service 

11extensions are NOT the same thing as the option keywords for the RCPT 

12and MAIL commands! 

13 

14Example: 

15 

16 >>> import smtplib 

17 >>> s=smtplib.SMTP("localhost") 

18 >>> print(s.help()) 

19 This is Sendmail version 8.8.4 

20 Topics: 

21 HELO EHLO MAIL RCPT DATA 

22 RSET NOOP QUIT HELP VRFY 

23 EXPN VERB ETRN DSN 

24 For more info use "HELP <topic>". 

25 To report bugs in the implementation send email to 

26 sendmail-bugs@sendmail.org. 

27 For local information send email to Postmaster at your site. 

28 End of HELP info 

29 >>> s.putcmd("vrfy","someone@here") 

30 >>> s.getreply() 

31 (250, "Somebody OverHere <somebody@here.my.org>") 

32 >>> s.quit() 

33''' 

34 

35# Author: The Dragon De Monsyne <dragondm@integral.org> 

36# ESMTP support, test code and doc fixes added by 

37# Eric S. Raymond <esr@thyrsus.com> 

38# Better RFC 821 compliance (MAIL and RCPT, and CRLF in data) 

39# by Carey Evans <c.evans@clear.net.nz>, for picky mail servers. 

40# RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>. 

41# 

42# This was modified from the Python 1.5 library HTTP lib. 

43 

44import socket 

45import io 

46import re 

47import email.utils 

48import email.message 

49import email.generator 

50import base64 

51import hmac 

52import copy 

53import datetime 

54import sys 

55from email.base64mime import body_encode as encode_base64 

56 

57__all__ = ["SMTPException", "SMTPNotSupportedError", "SMTPServerDisconnected", "SMTPResponseException", 

58 "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError", 

59 "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError", 

60 "quoteaddr", "quotedata", "SMTP"] 

61 

62SMTP_PORT = 25 

63SMTP_SSL_PORT = 465 

64CRLF = "\r\n" 

65bCRLF = b"\r\n" 

66_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3 

67 

68OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I) 

69 

70# Exception classes used by this module. 

71class SMTPException(OSError): 

72 """Base class for all exceptions raised by this module.""" 

73 

74class SMTPNotSupportedError(SMTPException): 

75 """The command or option is not supported by the SMTP server. 

76 

77 This exception is raised when an attempt is made to run a command or a 

78 command with an option which is not supported by the server. 

79 """ 

80 

81class SMTPServerDisconnected(SMTPException): 

82 """Not connected to any SMTP server. 

83 

84 This exception is raised when the server unexpectedly disconnects, 

85 or when an attempt is made to use the SMTP instance before 

86 connecting it to a server. 

87 """ 

88 

89class SMTPResponseException(SMTPException): 

90 """Base class for all exceptions that include an SMTP error code. 

91 

92 These exceptions are generated in some instances when the SMTP 

93 server returns an error code. The error code is stored in the 

94 `smtp_code' attribute of the error, and the `smtp_error' attribute 

95 is set to the error message. 

96 """ 

97 

98 def __init__(self, code, msg): 

99 self.smtp_code = code 

100 self.smtp_error = msg 

101 self.args = (code, msg) 

102 

103class SMTPSenderRefused(SMTPResponseException): 

104 """Sender address refused. 

105 

106 In addition to the attributes set by on all SMTPResponseException 

107 exceptions, this sets `sender' to the string that the SMTP refused. 

108 """ 

109 

110 def __init__(self, code, msg, sender): 

111 self.smtp_code = code 

112 self.smtp_error = msg 

113 self.sender = sender 

114 self.args = (code, msg, sender) 

115 

116class SMTPRecipientsRefused(SMTPException): 

117 """All recipient addresses refused. 

118 

119 The errors for each recipient are accessible through the attribute 

120 'recipients', which is a dictionary of exactly the same sort as 

121 SMTP.sendmail() returns. 

122 """ 

123 

124 def __init__(self, recipients): 

125 self.recipients = recipients 

126 self.args = (recipients,) 

127 

128 

129class SMTPDataError(SMTPResponseException): 

130 """The SMTP server didn't accept the data.""" 

131 

132class SMTPConnectError(SMTPResponseException): 

133 """Error during connection establishment.""" 

134 

135class SMTPHeloError(SMTPResponseException): 

136 """The server refused our HELO reply.""" 

137 

138class SMTPAuthenticationError(SMTPResponseException): 

139 """Authentication error. 

140 

141 Most probably the server didn't accept the username/password 

142 combination provided. 

143 """ 

144 

145def quoteaddr(addrstring): 

146 """Quote a subset of the email addresses defined by RFC 821. 

147 

148 Should be able to handle anything email.utils.parseaddr can handle. 

149 """ 

150 displayname, addr = email.utils.parseaddr(addrstring) 

151 if (displayname, addr) == ('', ''): 

152 # parseaddr couldn't parse it, use it as is and hope for the best. 

153 if addrstring.strip().startswith('<'): 

154 return addrstring 

155 return "<%s>" % addrstring 

156 return "<%s>" % addr 

157 

158def _addr_only(addrstring): 

159 displayname, addr = email.utils.parseaddr(addrstring) 

160 if (displayname, addr) == ('', ''): 

161 # parseaddr couldn't parse it, so use it as is. 

162 return addrstring 

163 return addr 

164 

165# Legacy method kept for backward compatibility. 

166def quotedata(data): 

167 """Quote data for email. 

168 

169 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into 

170 Internet CRLF end-of-line. 

171 """ 

172 return re.sub(r'(?m)^\.', '..', 

173 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) 

174 

175def _quote_periods(bindata): 

176 return re.sub(br'(?m)^\.', b'..', bindata) 

177 

178def _fix_eols(data): 

179 return re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data) 

180 

181try: 

182 import ssl 

183except ImportError: 

184 _have_ssl = False 

185else: 

186 _have_ssl = True 

187 

188 

189class SMTP: 

190 """This class manages a connection to an SMTP or ESMTP server. 

191 SMTP Objects: 

192 SMTP objects have the following attributes: 

193 helo_resp 

194 This is the message given by the server in response to the 

195 most recent HELO command. 

196 

197 ehlo_resp 

198 This is the message given by the server in response to the 

199 most recent EHLO command. This is usually multiline. 

200 

201 does_esmtp 

202 This is a True value _after you do an EHLO command_, if the 

203 server supports ESMTP. 

204 

205 esmtp_features 

206 This is a dictionary, which, if the server supports ESMTP, 

207 will _after you do an EHLO command_, contain the names of the 

208 SMTP service extensions this server supports, and their 

209 parameters (if any). 

210 

211 Note, all extension names are mapped to lower case in the 

212 dictionary. 

213 

214 See each method's docstrings for details. In general, there is a 

215 method of the same name to perform each SMTP command. There is also a 

216 method called 'sendmail' that will do an entire mail transaction. 

217 """ 

218 debuglevel = 0 

219 

220 sock = None 

221 file = None 

222 helo_resp = None 

223 ehlo_msg = "ehlo" 

224 ehlo_resp = None 

225 does_esmtp = 0 

226 default_port = SMTP_PORT 

227 

228 def __init__(self, host='', port=0, local_hostname=None, 

229 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 

230 source_address=None): 

231 """Initialize a new instance. 

232 

233 If specified, `host' is the name of the remote host to which to 

234 connect. If specified, `port' specifies the port to which to connect. 

235 By default, smtplib.SMTP_PORT is used. If a host is specified the 

236 connect method is called, and if it returns anything other than a 

237 success code an SMTPConnectError is raised. If specified, 

238 `local_hostname` is used as the FQDN of the local host in the HELO/EHLO 

239 command. Otherwise, the local hostname is found using 

240 socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host, 

241 port) for the socket to bind to as its source address before 

242 connecting. If the host is '' and port is 0, the OS default behavior 

243 will be used. 

244 

245 """ 

246 self._host = host 

247 self.timeout = timeout 

248 self.esmtp_features = {} 

249 self.command_encoding = 'ascii' 

250 self.source_address = source_address 

251 

252 if host: 

253 (code, msg) = self.connect(host, port) 

254 if code != 220: 

255 self.close() 

256 raise SMTPConnectError(code, msg) 

257 if local_hostname is not None: 

258 self.local_hostname = local_hostname 

259 else: 

260 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and 

261 # if that can't be calculated, that we should use a domain literal 

262 # instead (essentially an encoded IP address like [A.B.C.D]). 

263 fqdn = socket.getfqdn() 

264 if '.' in fqdn: 

265 self.local_hostname = fqdn 

266 else: 

267 # We can't find an fqdn hostname, so use a domain literal 

268 addr = '127.0.0.1' 

269 try: 

270 addr = socket.gethostbyname(socket.gethostname()) 

271 except socket.gaierror: 

272 pass 

273 self.local_hostname = '[%s]' % addr 

274 

275 def __enter__(self): 

276 return self 

277 

278 def __exit__(self, *args): 

279 try: 

280 code, message = self.docmd("QUIT") 

281 if code != 221: 

282 raise SMTPResponseException(code, message) 

283 except SMTPServerDisconnected: 

284 pass 

285 finally: 

286 self.close() 

287 

288 def set_debuglevel(self, debuglevel): 

289 """Set the debug output level. 

290 

291 A non-false value results in debug messages for connection and for all 

292 messages sent to and received from the server. 

293 

294 """ 

295 self.debuglevel = debuglevel 

296 

297 def _print_debug(self, *args): 

298 if self.debuglevel > 1: 

299 print(datetime.datetime.now().time(), *args, file=sys.stderr) 

300 else: 

301 print(*args, file=sys.stderr) 

302 

303 def _get_socket(self, host, port, timeout): 

304 # This makes it simpler for SMTP_SSL to use the SMTP connect code 

305 # and just alter the socket connection bit. 

306 if self.debuglevel > 0: 

307 self._print_debug('connect: to', (host, port), self.source_address) 

308 return socket.create_connection((host, port), timeout, 

309 self.source_address) 

310 

311 def connect(self, host='localhost', port=0, source_address=None): 

312 """Connect to a host on a given port. 

313 

314 If the hostname ends with a colon (`:') followed by a number, and 

315 there is no port specified, that suffix will be stripped off and the 

316 number interpreted as the port number to use. 

317 

318 Note: This method is automatically invoked by __init__, if a host is 

319 specified during instantiation. 

320 

321 """ 

322 

323 if source_address: 

324 self.source_address = source_address 

325 

326 if not port and (host.find(':') == host.rfind(':')): 

327 i = host.rfind(':') 

328 if i >= 0: 

329 host, port = host[:i], host[i + 1:] 

330 try: 

331 port = int(port) 

332 except ValueError: 

333 raise OSError("nonnumeric port") 

334 if not port: 

335 port = self.default_port 

336 if self.debuglevel > 0: 

337 self._print_debug('connect:', (host, port)) 

338 sys.audit("smtplib.connect", self, host, port) 

339 self.sock = self._get_socket(host, port, self.timeout) 

340 self.file = None 

341 (code, msg) = self.getreply() 

342 if self.debuglevel > 0: 

343 self._print_debug('connect:', repr(msg)) 

344 return (code, msg) 

345 

346 def send(self, s): 

347 """Send `s' to the server.""" 

348 if self.debuglevel > 0: 

349 self._print_debug('send:', repr(s)) 

350 if self.sock: 

351 if isinstance(s, str): 

352 # send is used by the 'data' command, where command_encoding 

353 # should not be used, but 'data' needs to convert the string to 

354 # binary itself anyway, so that's not a problem. 

355 s = s.encode(self.command_encoding) 

356 sys.audit("smtplib.send", self, s) 

357 try: 

358 self.sock.sendall(s) 

359 except OSError: 

360 self.close() 

361 raise SMTPServerDisconnected('Server not connected') 

362 else: 

363 raise SMTPServerDisconnected('please run connect() first') 

364 

365 def putcmd(self, cmd, args=""): 

366 """Send a command to the server.""" 

367 if args == "": 

368 str = '%s%s' % (cmd, CRLF) 

369 else: 

370 str = '%s %s%s' % (cmd, args, CRLF) 

371 self.send(str) 

372 

373 def getreply(self): 

374 """Get a reply from the server. 

375 

376 Returns a tuple consisting of: 

377 

378 - server response code (e.g. '250', or such, if all goes well) 

379 Note: returns -1 if it can't read response code. 

380 

381 - server response string corresponding to response code (multiline 

382 responses are converted to a single, multiline string). 

383 

384 Raises SMTPServerDisconnected if end-of-file is reached. 

385 """ 

386 resp = [] 

387 if self.file is None: 

388 self.file = self.sock.makefile('rb') 

389 while 1: 

390 try: 

391 line = self.file.readline(_MAXLINE + 1) 

392 except OSError as e: 

393 self.close() 

394 raise SMTPServerDisconnected("Connection unexpectedly closed: " 

395 + str(e)) 

396 if not line: 

397 self.close() 

398 raise SMTPServerDisconnected("Connection unexpectedly closed") 

399 if self.debuglevel > 0: 

400 self._print_debug('reply:', repr(line)) 

401 if len(line) > _MAXLINE: 

402 self.close() 

403 raise SMTPResponseException(500, "Line too long.") 

404 resp.append(line[4:].strip(b' \t\r\n')) 

405 code = line[:3] 

406 # Check that the error code is syntactically correct. 

407 # Don't attempt to read a continuation line if it is broken. 

408 try: 

409 errcode = int(code) 

410 except ValueError: 

411 errcode = -1 

412 break 

413 # Check if multiline response. 

414 if line[3:4] != b"-": 

415 break 

416 

417 errmsg = b"\n".join(resp) 

418 if self.debuglevel > 0: 

419 self._print_debug('reply: retcode (%s); Msg: %a' % (errcode, errmsg)) 

420 return errcode, errmsg 

421 

422 def docmd(self, cmd, args=""): 

423 """Send a command, and return its response code.""" 

424 self.putcmd(cmd, args) 

425 return self.getreply() 

426 

427 # std smtp commands 

428 def helo(self, name=''): 

429 """SMTP 'helo' command. 

430 Hostname to send for this command defaults to the FQDN of the local 

431 host. 

432 """ 

433 self.putcmd("helo", name or self.local_hostname) 

434 (code, msg) = self.getreply() 

435 self.helo_resp = msg 

436 return (code, msg) 

437 

438 def ehlo(self, name=''): 

439 """ SMTP 'ehlo' command. 

440 Hostname to send for this command defaults to the FQDN of the local 

441 host. 

442 """ 

443 self.esmtp_features = {} 

444 self.putcmd(self.ehlo_msg, name or self.local_hostname) 

445 (code, msg) = self.getreply() 

446 # According to RFC1869 some (badly written) 

447 # MTA's will disconnect on an ehlo. Toss an exception if 

448 # that happens -ddm 

449 if code == -1 and len(msg) == 0: 

450 self.close() 

451 raise SMTPServerDisconnected("Server not connected") 

452 self.ehlo_resp = msg 

453 if code != 250: 

454 return (code, msg) 

455 self.does_esmtp = 1 

456 #parse the ehlo response -ddm 

457 assert isinstance(self.ehlo_resp, bytes), repr(self.ehlo_resp) 

458 resp = self.ehlo_resp.decode("latin-1").split('\n') 

459 del resp[0] 

460 for each in resp: 

461 # To be able to communicate with as many SMTP servers as possible, 

462 # we have to take the old-style auth advertisement into account, 

463 # because: 

464 # 1) Else our SMTP feature parser gets confused. 

465 # 2) There are some servers that only advertise the auth methods we 

466 # support using the old style. 

467 auth_match = OLDSTYLE_AUTH.match(each) 

468 if auth_match: 

469 # This doesn't remove duplicates, but that's no problem 

470 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ 

471 + " " + auth_match.groups(0)[0] 

472 continue 

473 

474 # RFC 1869 requires a space between ehlo keyword and parameters. 

475 # It's actually stricter, in that only spaces are allowed between 

476 # parameters, but were not going to check for that here. Note 

477 # that the space isn't present if there are no parameters. 

478 m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each) 

479 if m: 

480 feature = m.group("feature").lower() 

481 params = m.string[m.end("feature"):].strip() 

482 if feature == "auth": 

483 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ 

484 + " " + params 

485 else: 

486 self.esmtp_features[feature] = params 

487 return (code, msg) 

488 

489 def has_extn(self, opt): 

490 """Does the server support a given SMTP service extension?""" 

491 return opt.lower() in self.esmtp_features 

492 

493 def help(self, args=''): 

494 """SMTP 'help' command. 

495 Returns help text from server.""" 

496 self.putcmd("help", args) 

497 return self.getreply()[1] 

498 

499 def rset(self): 

500 """SMTP 'rset' command -- resets session.""" 

501 self.command_encoding = 'ascii' 

502 return self.docmd("rset") 

503 

504 def _rset(self): 

505 """Internal 'rset' command which ignores any SMTPServerDisconnected error. 

506 

507 Used internally in the library, since the server disconnected error 

508 should appear to the application when the *next* command is issued, if 

509 we are doing an internal "safety" reset. 

510 """ 

511 try: 

512 self.rset() 

513 except SMTPServerDisconnected: 

514 pass 

515 

516 def noop(self): 

517 """SMTP 'noop' command -- doesn't do anything :>""" 

518 return self.docmd("noop") 

519 

520 def mail(self, sender, options=()): 

521 """SMTP 'mail' command -- begins mail xfer session. 

522 

523 This method may raise the following exceptions: 

524 

525 SMTPNotSupportedError The options parameter includes 'SMTPUTF8' 

526 but the SMTPUTF8 extension is not supported by 

527 the server. 

528 """ 

529 optionlist = '' 

530 if options and self.does_esmtp: 

531 if any(x.lower()=='smtputf8' for x in options): 

532 if self.has_extn('smtputf8'): 

533 self.command_encoding = 'utf-8' 

534 else: 

535 raise SMTPNotSupportedError( 

536 'SMTPUTF8 not supported by server') 

537 optionlist = ' ' + ' '.join(options) 

538 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist)) 

539 return self.getreply() 

540 

541 def rcpt(self, recip, options=()): 

542 """SMTP 'rcpt' command -- indicates 1 recipient for this mail.""" 

543 optionlist = '' 

544 if options and self.does_esmtp: 

545 optionlist = ' ' + ' '.join(options) 

546 self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist)) 

547 return self.getreply() 

548 

549 def data(self, msg): 

550 """SMTP 'DATA' command -- sends message data to server. 

551 

552 Automatically quotes lines beginning with a period per rfc821. 

553 Raises SMTPDataError if there is an unexpected reply to the 

554 DATA command; the return value from this method is the final 

555 response code received when the all data is sent. If msg 

556 is a string, lone '\\r' and '\\n' characters are converted to 

557 '\\r\\n' characters. If msg is bytes, it is transmitted as is. 

558 """ 

559 self.putcmd("data") 

560 (code, repl) = self.getreply() 

561 if self.debuglevel > 0: 

562 self._print_debug('data:', (code, repl)) 

563 if code != 354: 

564 raise SMTPDataError(code, repl) 

565 else: 

566 if isinstance(msg, str): 

567 msg = _fix_eols(msg).encode('ascii') 

568 q = _quote_periods(msg) 

569 if q[-2:] != bCRLF: 

570 q = q + bCRLF 

571 q = q + b"." + bCRLF 

572 self.send(q) 

573 (code, msg) = self.getreply() 

574 if self.debuglevel > 0: 

575 self._print_debug('data:', (code, msg)) 

576 return (code, msg) 

577 

578 def verify(self, address): 

579 """SMTP 'verify' command -- checks for address validity.""" 

580 self.putcmd("vrfy", _addr_only(address)) 

581 return self.getreply() 

582 # a.k.a. 

583 vrfy = verify 

584 

585 def expn(self, address): 

586 """SMTP 'expn' command -- expands a mailing list.""" 

587 self.putcmd("expn", _addr_only(address)) 

588 return self.getreply() 

589 

590 # some useful methods 

591 

592 def ehlo_or_helo_if_needed(self): 

593 """Call self.ehlo() and/or self.helo() if needed. 

594 

595 If there has been no previous EHLO or HELO command this session, this 

596 method tries ESMTP EHLO first. 

597 

598 This method may raise the following exceptions: 

599 

600 SMTPHeloError The server didn't reply properly to 

601 the helo greeting. 

602 """ 

603 if self.helo_resp is None and self.ehlo_resp is None: 

604 if not (200 <= self.ehlo()[0] <= 299): 

605 (code, resp) = self.helo() 

606 if not (200 <= code <= 299): 

607 raise SMTPHeloError(code, resp) 

608 

609 def auth(self, mechanism, authobject, *, initial_response_ok=True): 

610 """Authentication command - requires response processing. 

611 

612 'mechanism' specifies which authentication mechanism is to 

613 be used - the valid values are those listed in the 'auth' 

614 element of 'esmtp_features'. 

615 

616 'authobject' must be a callable object taking a single argument: 

617 

618 data = authobject(challenge) 

619 

620 It will be called to process the server's challenge response; the 

621 challenge argument it is passed will be a bytes. It should return 

622 an ASCII string that will be base64 encoded and sent to the server. 

623 

624 Keyword arguments: 

625 - initial_response_ok: Allow sending the RFC 4954 initial-response 

626 to the AUTH command, if the authentication methods supports it. 

627 """ 

628 # RFC 4954 allows auth methods to provide an initial response. Not all 

629 # methods support it. By definition, if they return something other 

630 # than None when challenge is None, then they do. See issue #15014. 

631 mechanism = mechanism.upper() 

632 initial_response = (authobject() if initial_response_ok else None) 

633 if initial_response is not None: 

634 response = encode_base64(initial_response.encode('ascii'), eol='') 

635 (code, resp) = self.docmd("AUTH", mechanism + " " + response) 

636 else: 

637 (code, resp) = self.docmd("AUTH", mechanism) 

638 # If server responds with a challenge, send the response. 

639 if code == 334: 

640 challenge = base64.decodebytes(resp) 

641 response = encode_base64( 

642 authobject(challenge).encode('ascii'), eol='') 

643 (code, resp) = self.docmd(response) 

644 if code in (235, 503): 

645 return (code, resp) 

646 raise SMTPAuthenticationError(code, resp) 

647 

648 def auth_cram_md5(self, challenge=None): 

649 """ Authobject to use with CRAM-MD5 authentication. Requires self.user 

650 and self.password to be set.""" 

651 # CRAM-MD5 does not support initial-response. 

652 if challenge is None: 

653 return None 

654 return self.user + " " + hmac.HMAC( 

655 self.password.encode('ascii'), challenge, 'md5').hexdigest() 

656 

657 def auth_plain(self, challenge=None): 

658 """ Authobject to use with PLAIN authentication. Requires self.user and 

659 self.password to be set.""" 

660 return "\0%s\0%s" % (self.user, self.password) 

661 

662 def auth_login(self, challenge=None): 

663 """ Authobject to use with LOGIN authentication. Requires self.user and 

664 self.password to be set.""" 

665 if challenge is None: 

666 return self.user 

667 else: 

668 return self.password 

669 

670 def login(self, user, password, *, initial_response_ok=True): 

671 """Log in on an SMTP server that requires authentication. 

672 

673 The arguments are: 

674 - user: The user name to authenticate with. 

675 - password: The password for the authentication. 

676 

677 Keyword arguments: 

678 - initial_response_ok: Allow sending the RFC 4954 initial-response 

679 to the AUTH command, if the authentication methods supports it. 

680 

681 If there has been no previous EHLO or HELO command this session, this 

682 method tries ESMTP EHLO first. 

683 

684 This method will return normally if the authentication was successful. 

685 

686 This method may raise the following exceptions: 

687 

688 SMTPHeloError The server didn't reply properly to 

689 the helo greeting. 

690 SMTPAuthenticationError The server didn't accept the username/ 

691 password combination. 

692 SMTPNotSupportedError The AUTH command is not supported by the 

693 server. 

694 SMTPException No suitable authentication method was 

695 found. 

696 """ 

697 

698 self.ehlo_or_helo_if_needed() 

699 if not self.has_extn("auth"): 

700 raise SMTPNotSupportedError( 

701 "SMTP AUTH extension not supported by server.") 

702 

703 # Authentication methods the server claims to support 

704 advertised_authlist = self.esmtp_features["auth"].split() 

705 

706 # Authentication methods we can handle in our preferred order: 

707 preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN'] 

708 

709 # We try the supported authentications in our preferred order, if 

710 # the server supports them. 

711 authlist = [auth for auth in preferred_auths 

712 if auth in advertised_authlist] 

713 if not authlist: 

714 raise SMTPException("No suitable authentication method found.") 

715 

716 # Some servers advertise authentication methods they don't really 

717 # support, so if authentication fails, we continue until we've tried 

718 # all methods. 

719 self.user, self.password = user, password 

720 for authmethod in authlist: 

721 method_name = 'auth_' + authmethod.lower().replace('-', '_') 

722 try: 

723 (code, resp) = self.auth( 

724 authmethod, getattr(self, method_name), 

725 initial_response_ok=initial_response_ok) 

726 # 235 == 'Authentication successful' 

727 # 503 == 'Error: already authenticated' 

728 if code in (235, 503): 

729 return (code, resp) 

730 except SMTPAuthenticationError as e: 

731 last_exception = e 

732 

733 # We could not login successfully. Return result of last attempt. 

734 raise last_exception 

735 

736 def starttls(self, keyfile=None, certfile=None, context=None): 

737 """Puts the connection to the SMTP server into TLS mode. 

738 

739 If there has been no previous EHLO or HELO command this session, this 

740 method tries ESMTP EHLO first. 

741 

742 If the server supports TLS, this will encrypt the rest of the SMTP 

743 session. If you provide the keyfile and certfile parameters, 

744 the identity of the SMTP server and client can be checked. This, 

745 however, depends on whether the socket module really checks the 

746 certificates. 

747 

748 This method may raise the following exceptions: 

749 

750 SMTPHeloError The server didn't reply properly to 

751 the helo greeting. 

752 """ 

753 self.ehlo_or_helo_if_needed() 

754 if not self.has_extn("starttls"): 

755 raise SMTPNotSupportedError( 

756 "STARTTLS extension not supported by server.") 

757 (resp, reply) = self.docmd("STARTTLS") 

758 if resp == 220: 

759 if not _have_ssl: 

760 raise RuntimeError("No SSL support included in this Python") 

761 if context is not None and keyfile is not None: 

762 raise ValueError("context and keyfile arguments are mutually " 

763 "exclusive") 

764 if context is not None and certfile is not None: 

765 raise ValueError("context and certfile arguments are mutually " 

766 "exclusive") 

767 if keyfile is not None or certfile is not None: 

768 import warnings 

769 warnings.warn("keyfile and certfile are deprecated, use a " 

770 "custom context instead", DeprecationWarning, 2) 

771 if context is None: 

772 context = ssl._create_stdlib_context(certfile=certfile, 

773 keyfile=keyfile) 

774 self.sock = context.wrap_socket(self.sock, 

775 server_hostname=self._host) 

776 self.file = None 

777 # RFC 3207: 

778 # The client MUST discard any knowledge obtained from 

779 # the server, such as the list of SMTP service extensions, 

780 # which was not obtained from the TLS negotiation itself. 

781 self.helo_resp = None 

782 self.ehlo_resp = None 

783 self.esmtp_features = {} 

784 self.does_esmtp = 0 

785 else: 

786 # RFC 3207: 

787 # 501 Syntax error (no parameters allowed) 

788 # 454 TLS not available due to temporary reason 

789 raise SMTPResponseException(resp, reply) 

790 return (resp, reply) 

791 

792 def sendmail(self, from_addr, to_addrs, msg, mail_options=(), 

793 rcpt_options=()): 

794 """This command performs an entire mail transaction. 

795 

796 The arguments are: 

797 - from_addr : The address sending this mail. 

798 - to_addrs : A list of addresses to send this mail to. A bare 

799 string will be treated as a list with 1 address. 

800 - msg : The message to send. 

801 - mail_options : List of ESMTP options (such as 8bitmime) for the 

802 mail command. 

803 - rcpt_options : List of ESMTP options (such as DSN commands) for 

804 all the rcpt commands. 

805 

806 msg may be a string containing characters in the ASCII range, or a byte 

807 string. A string is encoded to bytes using the ascii codec, and lone 

808 \\r and \\n characters are converted to \\r\\n characters. 

809 

810 If there has been no previous EHLO or HELO command this session, this 

811 method tries ESMTP EHLO first. If the server does ESMTP, message size 

812 and each of the specified options will be passed to it. If EHLO 

813 fails, HELO will be tried and ESMTP options suppressed. 

814 

815 This method will return normally if the mail is accepted for at least 

816 one recipient. It returns a dictionary, with one entry for each 

817 recipient that was refused. Each entry contains a tuple of the SMTP 

818 error code and the accompanying error message sent by the server. 

819 

820 This method may raise the following exceptions: 

821 

822 SMTPHeloError The server didn't reply properly to 

823 the helo greeting. 

824 SMTPRecipientsRefused The server rejected ALL recipients 

825 (no mail was sent). 

826 SMTPSenderRefused The server didn't accept the from_addr. 

827 SMTPDataError The server replied with an unexpected 

828 error code (other than a refusal of 

829 a recipient). 

830 SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8' 

831 but the SMTPUTF8 extension is not supported by 

832 the server. 

833 

834 Note: the connection will be open even after an exception is raised. 

835 

836 Example: 

837 

838 >>> import smtplib 

839 >>> s=smtplib.SMTP("localhost") 

840 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] 

841 >>> msg = '''\\ 

842 ... From: Me@my.org 

843 ... Subject: testin'... 

844 ... 

845 ... This is a test ''' 

846 >>> s.sendmail("me@my.org",tolist,msg) 

847 { "three@three.org" : ( 550 ,"User unknown" ) } 

848 >>> s.quit() 

849 

850 In the above example, the message was accepted for delivery to three 

851 of the four addresses, and one was rejected, with the error code 

852 550. If all addresses are accepted, then the method will return an 

853 empty dictionary. 

854 

855 """ 

856 self.ehlo_or_helo_if_needed() 

857 esmtp_opts = [] 

858 if isinstance(msg, str): 

859 msg = _fix_eols(msg).encode('ascii') 

860 if self.does_esmtp: 

861 if self.has_extn('size'): 

862 esmtp_opts.append("size=%d" % len(msg)) 

863 for option in mail_options: 

864 esmtp_opts.append(option) 

865 (code, resp) = self.mail(from_addr, esmtp_opts) 

866 if code != 250: 

867 if code == 421: 

868 self.close() 

869 else: 

870 self._rset() 

871 raise SMTPSenderRefused(code, resp, from_addr) 

872 senderrs = {} 

873 if isinstance(to_addrs, str): 

874 to_addrs = [to_addrs] 

875 for each in to_addrs: 

876 (code, resp) = self.rcpt(each, rcpt_options) 

877 if (code != 250) and (code != 251): 

878 senderrs[each] = (code, resp) 

879 if code == 421: 

880 self.close() 

881 raise SMTPRecipientsRefused(senderrs) 

882 if len(senderrs) == len(to_addrs): 

883 # the server refused all our recipients 

884 self._rset() 

885 raise SMTPRecipientsRefused(senderrs) 

886 (code, resp) = self.data(msg) 

887 if code != 250: 

888 if code == 421: 

889 self.close() 

890 else: 

891 self._rset() 

892 raise SMTPDataError(code, resp) 

893 #if we got here then somebody got our mail 

894 return senderrs 

895 

896 def send_message(self, msg, from_addr=None, to_addrs=None, 

897 mail_options=(), rcpt_options=()): 

898 """Converts message to a bytestring and passes it to sendmail. 

899 

900 The arguments are as for sendmail, except that msg is an 

901 email.message.Message object. If from_addr is None or to_addrs is 

902 None, these arguments are taken from the headers of the Message as 

903 described in RFC 2822 (a ValueError is raised if there is more than 

904 one set of 'Resent-' headers). Regardless of the values of from_addr and 

905 to_addr, any Bcc field (or Resent-Bcc field, when the Message is a 

906 resent) of the Message object won't be transmitted. The Message 

907 object is then serialized using email.generator.BytesGenerator and 

908 sendmail is called to transmit the message. If the sender or any of 

909 the recipient addresses contain non-ASCII and the server advertises the 

910 SMTPUTF8 capability, the policy is cloned with utf8 set to True for the 

911 serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send. 

912 If the server does not support SMTPUTF8, an SMTPNotSupported error is 

913 raised. Otherwise the generator is called without modifying the 

914 policy. 

915 

916 """ 

917 # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822 

918 # Section 3.6.6). In such a case, we use the 'Resent-*' fields. However, 

919 # if there is more than one 'Resent-' block there's no way to 

920 # unambiguously determine which one is the most recent in all cases, 

921 # so rather than guess we raise a ValueError in that case. 

922 # 

923 # TODO implement heuristics to guess the correct Resent-* block with an 

924 # option allowing the user to enable the heuristics. (It should be 

925 # possible to guess correctly almost all of the time.) 

926 

927 self.ehlo_or_helo_if_needed() 

928 resent = msg.get_all('Resent-Date') 

929 if resent is None: 

930 header_prefix = '' 

931 elif len(resent) == 1: 

932 header_prefix = 'Resent-' 

933 else: 

934 raise ValueError("message has more than one 'Resent-' header block") 

935 if from_addr is None: 

936 # Prefer the sender field per RFC 2822:3.6.2. 

937 from_addr = (msg[header_prefix + 'Sender'] 

938 if (header_prefix + 'Sender') in msg 

939 else msg[header_prefix + 'From']) 

940 from_addr = email.utils.getaddresses([from_addr])[0][1] 

941 if to_addrs is None: 

942 addr_fields = [f for f in (msg[header_prefix + 'To'], 

943 msg[header_prefix + 'Bcc'], 

944 msg[header_prefix + 'Cc']) 

945 if f is not None] 

946 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)] 

947 # Make a local copy so we can delete the bcc headers. 

948 msg_copy = copy.copy(msg) 

949 del msg_copy['Bcc'] 

950 del msg_copy['Resent-Bcc'] 

951 international = False 

952 try: 

953 ''.join([from_addr, *to_addrs]).encode('ascii') 

954 except UnicodeEncodeError: 

955 if not self.has_extn('smtputf8'): 

956 raise SMTPNotSupportedError( 

957 "One or more source or delivery addresses require" 

958 " internationalized email support, but the server" 

959 " does not advertise the required SMTPUTF8 capability") 

960 international = True 

961 with io.BytesIO() as bytesmsg: 

962 if international: 

963 g = email.generator.BytesGenerator( 

964 bytesmsg, policy=msg.policy.clone(utf8=True)) 

965 mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME') 

966 else: 

967 g = email.generator.BytesGenerator(bytesmsg) 

968 g.flatten(msg_copy, linesep='\r\n') 

969 flatmsg = bytesmsg.getvalue() 

970 return self.sendmail(from_addr, to_addrs, flatmsg, mail_options, 

971 rcpt_options) 

972 

973 def close(self): 

974 """Close the connection to the SMTP server.""" 

975 try: 

976 file = self.file 

977 self.file = None 

978 if file: 

979 file.close() 

980 finally: 

981 sock = self.sock 

982 self.sock = None 

983 if sock: 

984 sock.close() 

985 

986 def quit(self): 

987 """Terminate the SMTP session.""" 

988 res = self.docmd("quit") 

989 # A new EHLO is required after reconnecting with connect() 

990 self.ehlo_resp = self.helo_resp = None 

991 self.esmtp_features = {} 

992 self.does_esmtp = False 

993 self.close() 

994 return res 

995 

996if _have_ssl: 

997 

998 class SMTP_SSL(SMTP): 

999 """ This is a subclass derived from SMTP that connects over an SSL 

1000 encrypted socket (to use this class you need a socket module that was 

1001 compiled with SSL support). If host is not specified, '' (the local 

1002 host) is used. If port is omitted, the standard SMTP-over-SSL port 

1003 (465) is used. local_hostname and source_address have the same meaning 

1004 as they do in the SMTP class. keyfile and certfile are also optional - 

1005 they can contain a PEM formatted private key and certificate chain file 

1006 for the SSL connection. context also optional, can contain a 

1007 SSLContext, and is an alternative to keyfile and certfile; If it is 

1008 specified both keyfile and certfile must be None. 

1009 

1010 """ 

1011 

1012 default_port = SMTP_SSL_PORT 

1013 

1014 def __init__(self, host='', port=0, local_hostname=None, 

1015 keyfile=None, certfile=None, 

1016 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 

1017 source_address=None, context=None): 

1018 if context is not None and keyfile is not None: 

1019 raise ValueError("context and keyfile arguments are mutually " 

1020 "exclusive") 

1021 if context is not None and certfile is not None: 

1022 raise ValueError("context and certfile arguments are mutually " 

1023 "exclusive") 

1024 if keyfile is not None or certfile is not None: 

1025 import warnings 

1026 warnings.warn("keyfile and certfile are deprecated, use a " 

1027 "custom context instead", DeprecationWarning, 2) 

1028 self.keyfile = keyfile 

1029 self.certfile = certfile 

1030 if context is None: 

1031 context = ssl._create_stdlib_context(certfile=certfile, 

1032 keyfile=keyfile) 

1033 self.context = context 

1034 SMTP.__init__(self, host, port, local_hostname, timeout, 

1035 source_address) 

1036 

1037 def _get_socket(self, host, port, timeout): 

1038 if self.debuglevel > 0: 

1039 self._print_debug('connect:', (host, port)) 

1040 new_socket = socket.create_connection((host, port), timeout, 

1041 self.source_address) 

1042 new_socket = self.context.wrap_socket(new_socket, 

1043 server_hostname=self._host) 

1044 return new_socket 

1045 

1046 __all__.append("SMTP_SSL") 

1047 

1048# 

1049# LMTP extension 

1050# 

1051LMTP_PORT = 2003 

1052 

1053class LMTP(SMTP): 

1054 """LMTP - Local Mail Transfer Protocol 

1055 

1056 The LMTP protocol, which is very similar to ESMTP, is heavily based 

1057 on the standard SMTP client. It's common to use Unix sockets for 

1058 LMTP, so our connect() method must support that as well as a regular 

1059 host:port server. local_hostname and source_address have the same 

1060 meaning as they do in the SMTP class. To specify a Unix socket, 

1061 you must use an absolute path as the host, starting with a '/'. 

1062 

1063 Authentication is supported, using the regular SMTP mechanism. When 

1064 using a Unix socket, LMTP generally don't support or require any 

1065 authentication, but your mileage might vary.""" 

1066 

1067 ehlo_msg = "lhlo" 

1068 

1069 def __init__(self, host='', port=LMTP_PORT, local_hostname=None, 

1070 source_address=None): 

1071 """Initialize a new instance.""" 

1072 SMTP.__init__(self, host, port, local_hostname=local_hostname, 

1073 source_address=source_address) 

1074 

1075 def connect(self, host='localhost', port=0, source_address=None): 

1076 """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" 

1077 if host[0] != '/': 

1078 return SMTP.connect(self, host, port, source_address=source_address) 

1079 

1080 # Handle Unix-domain sockets. 

1081 try: 

1082 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 

1083 self.file = None 

1084 self.sock.connect(host) 

1085 except OSError: 

1086 if self.debuglevel > 0: 

1087 self._print_debug('connect fail:', host) 

1088 if self.sock: 

1089 self.sock.close() 

1090 self.sock = None 

1091 raise 

1092 (code, msg) = self.getreply() 

1093 if self.debuglevel > 0: 

1094 self._print_debug('connect:', msg) 

1095 return (code, msg) 

1096 

1097 

1098# Test the sendmail method, which tests most of the others. 

1099# Note: This always sends to localhost. 

1100if __name__ == '__main__': 

1101 def prompt(prompt): 

1102 sys.stdout.write(prompt + ": ") 

1103 sys.stdout.flush() 

1104 return sys.stdin.readline().strip() 

1105 

1106 fromaddr = prompt("From") 

1107 toaddrs = prompt("To").split(',') 

1108 print("Enter message, end with ^D:") 

1109 msg = '' 

1110 while 1: 

1111 line = sys.stdin.readline() 

1112 if not line: 

1113 break 

1114 msg = msg + line 

1115 print("Message length is %d" % len(msg)) 

1116 

1117 server = SMTP('localhost') 

1118 server.set_debuglevel(1) 

1119 server.sendmail(fromaddr, toaddrs, msg) 

1120 server.quit()