1#! /usr/bin/env python 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 re 46import email.utils 47import base64 48import hmac 49from email.base64mime import encode as encode_base64 50from sys import stderr 51 52__all__ = ["SMTPException", "SMTPServerDisconnected", "SMTPResponseException", 53 "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError", 54 "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError", 55 "quoteaddr", "quotedata", "SMTP"] 56 57SMTP_PORT = 25 58SMTP_SSL_PORT = 465 59CRLF = "\r\n" 60_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3 61 62OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I) 63 64 65# Exception classes used by this module. 66class SMTPException(Exception): 67 """Base class for all exceptions raised by this module.""" 68 69class SMTPServerDisconnected(SMTPException): 70 """Not connected to any SMTP server. 71 72 This exception is raised when the server unexpectedly disconnects, 73 or when an attempt is made to use the SMTP instance before 74 connecting it to a server. 75 """ 76 77class SMTPResponseException(SMTPException): 78 """Base class for all exceptions that include an SMTP error code. 79 80 These exceptions are generated in some instances when the SMTP 81 server returns an error code. The error code is stored in the 82 `smtp_code' attribute of the error, and the `smtp_error' attribute 83 is set to the error message. 84 """ 85 86 def __init__(self, code, msg): 87 self.smtp_code = code 88 self.smtp_error = msg 89 self.args = (code, msg) 90 91class SMTPSenderRefused(SMTPResponseException): 92 """Sender address refused. 93 94 In addition to the attributes set by on all SMTPResponseException 95 exceptions, this sets `sender' to the string that the SMTP refused. 96 """ 97 98 def __init__(self, code, msg, sender): 99 self.smtp_code = code 100 self.smtp_error = msg 101 self.sender = sender 102 self.args = (code, msg, sender) 103 104class SMTPRecipientsRefused(SMTPException): 105 """All recipient addresses refused. 106 107 The errors for each recipient are accessible through the attribute 108 'recipients', which is a dictionary of exactly the same sort as 109 SMTP.sendmail() returns. 110 """ 111 112 def __init__(self, recipients): 113 self.recipients = recipients 114 self.args = (recipients,) 115 116 117class SMTPDataError(SMTPResponseException): 118 """The SMTP server didn't accept the data.""" 119 120class SMTPConnectError(SMTPResponseException): 121 """Error during connection establishment.""" 122 123class SMTPHeloError(SMTPResponseException): 124 """The server refused our HELO reply.""" 125 126class SMTPAuthenticationError(SMTPResponseException): 127 """Authentication error. 128 129 Most probably the server didn't accept the username/password 130 combination provided. 131 """ 132 133 134def quoteaddr(addr): 135 """Quote a subset of the email addresses defined by RFC 821. 136 137 Should be able to handle anything rfc822.parseaddr can handle. 138 """ 139 m = (None, None) 140 try: 141 m = email.utils.parseaddr(addr)[1] 142 except AttributeError: 143 pass 144 if m == (None, None): # Indicates parse failure or AttributeError 145 # something weird here.. punt -ddm 146 return "<%s>" % addr 147 elif m is None: 148 # the sender wants an empty return address 149 return "<>" 150 else: 151 return "<%s>" % m 152 153def _addr_only(addrstring): 154 displayname, addr = email.utils.parseaddr(addrstring) 155 if (displayname, addr) == ('', ''): 156 # parseaddr couldn't parse it, so use it as is. 157 return addrstring 158 return addr 159 160def quotedata(data): 161 """Quote data for email. 162 163 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into 164 Internet CRLF end-of-line. 165 """ 166 return re.sub(r'(?m)^\.', '..', 167 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) 168 169 170try: 171 import ssl 172except ImportError: 173 _have_ssl = False 174else: 175 class SSLFakeFile: 176 """A fake file like object that really wraps a SSLObject. 177 178 It only supports what is needed in smtplib. 179 """ 180 def __init__(self, sslobj): 181 self.sslobj = sslobj 182 183 def readline(self, size=-1): 184 if size < 0: 185 size = None 186 str = "" 187 chr = None 188 while chr != "\n": 189 if size is not None and len(str) >= size: 190 break 191 chr = self.sslobj.read(1) 192 if not chr: 193 break 194 str += chr 195 return str 196 197 def close(self): 198 pass 199 200 _have_ssl = True 201 202class SMTP: 203 """This class manages a connection to an SMTP or ESMTP server. 204 SMTP Objects: 205 SMTP objects have the following attributes: 206 helo_resp 207 This is the message given by the server in response to the 208 most recent HELO command. 209 210 ehlo_resp 211 This is the message given by the server in response to the 212 most recent EHLO command. This is usually multiline. 213 214 does_esmtp 215 This is a True value _after you do an EHLO command_, if the 216 server supports ESMTP. 217 218 esmtp_features 219 This is a dictionary, which, if the server supports ESMTP, 220 will _after you do an EHLO command_, contain the names of the 221 SMTP service extensions this server supports, and their 222 parameters (if any). 223 224 Note, all extension names are mapped to lower case in the 225 dictionary. 226 227 See each method's docstrings for details. In general, there is a 228 method of the same name to perform each SMTP command. There is also a 229 method called 'sendmail' that will do an entire mail transaction. 230 """ 231 debuglevel = 0 232 file = None 233 helo_resp = None 234 ehlo_msg = "ehlo" 235 ehlo_resp = None 236 does_esmtp = 0 237 default_port = SMTP_PORT 238 239 def __init__(self, host='', port=0, local_hostname=None, 240 timeout=socket._GLOBAL_DEFAULT_TIMEOUT): 241 """Initialize a new instance. 242 243 If specified, `host' is the name of the remote host to which to 244 connect. If specified, `port' specifies the port to which to connect. 245 By default, smtplib.SMTP_PORT is used. If a host is specified the 246 connect method is called, and if it returns anything other than a 247 success code an SMTPConnectError is raised. If specified, 248 `local_hostname` is used as the FQDN of the local host for the 249 HELO/EHLO command. Otherwise, the local hostname is found using 250 socket.getfqdn(). 251 252 """ 253 self.timeout = timeout 254 self.esmtp_features = {} 255 if host: 256 (code, msg) = self.connect(host, port) 257 if code != 220: 258 self.close() 259 raise SMTPConnectError(code, msg) 260 if local_hostname is not None: 261 self.local_hostname = local_hostname 262 else: 263 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and 264 # if that can't be calculated, that we should use a domain literal 265 # instead (essentially an encoded IP address like [A.B.C.D]). 266 fqdn = socket.getfqdn() 267 if '.' in fqdn: 268 self.local_hostname = fqdn 269 else: 270 # We can't find an fqdn hostname, so use a domain literal 271 addr = '127.0.0.1' 272 try: 273 addr = socket.gethostbyname(socket.gethostname()) 274 except socket.gaierror: 275 pass 276 self.local_hostname = '[%s]' % addr 277 278 def set_debuglevel(self, debuglevel): 279 """Set the debug output level. 280 281 A non-false value results in debug messages for connection and for all 282 messages sent to and received from the server. 283 284 """ 285 self.debuglevel = debuglevel 286 287 def _get_socket(self, host, port, timeout): 288 # This makes it simpler for SMTP_SSL to use the SMTP connect code 289 # and just alter the socket connection bit. 290 if self.debuglevel > 0: 291 print>>stderr, 'connect:', (host, port) 292 return socket.create_connection((host, port), timeout) 293 294 def connect(self, host='localhost', port=0): 295 """Connect to a host on a given port. 296 297 If the hostname ends with a colon (`:') followed by a number, and 298 there is no port specified, that suffix will be stripped off and the 299 number interpreted as the port number to use. 300 301 Note: This method is automatically invoked by __init__, if a host is 302 specified during instantiation. 303 304 """ 305 if not port and (host.find(':') == host.rfind(':')): 306 i = host.rfind(':') 307 if i >= 0: 308 host, port = host[:i], host[i + 1:] 309 try: 310 port = int(port) 311 except ValueError: 312 raise socket.error, "nonnumeric port" 313 if not port: 314 port = self.default_port 315 if self.debuglevel > 0: 316 print>>stderr, 'connect:', (host, port) 317 self.sock = self._get_socket(host, port, self.timeout) 318 (code, msg) = self.getreply() 319 if self.debuglevel > 0: 320 print>>stderr, "connect:", msg 321 return (code, msg) 322 323 def send(self, str): 324 """Send `str' to the server.""" 325 if self.debuglevel > 0: 326 print>>stderr, 'send:', repr(str) 327 if hasattr(self, 'sock') and self.sock: 328 try: 329 self.sock.sendall(str) 330 except socket.error: 331 self.close() 332 raise SMTPServerDisconnected('Server not connected') 333 else: 334 raise SMTPServerDisconnected('please run connect() first') 335 336 def putcmd(self, cmd, args=""): 337 """Send a command to the server.""" 338 if args == "": 339 str = '%s%s' % (cmd, CRLF) 340 else: 341 str = '%s %s%s' % (cmd, args, CRLF) 342 self.send(str) 343 344 def getreply(self): 345 """Get a reply from the server. 346 347 Returns a tuple consisting of: 348 349 - server response code (e.g. '250', or such, if all goes well) 350 Note: returns -1 if it can't read response code. 351 352 - server response string corresponding to response code (multiline 353 responses are converted to a single, multiline string). 354 355 Raises SMTPServerDisconnected if end-of-file is reached. 356 """ 357 resp = [] 358 if self.file is None: 359 self.file = self.sock.makefile('rb') 360 while 1: 361 try: 362 line = self.file.readline(_MAXLINE + 1) 363 except socket.error as e: 364 self.close() 365 raise SMTPServerDisconnected("Connection unexpectedly closed: " 366 + str(e)) 367 if line == '': 368 self.close() 369 raise SMTPServerDisconnected("Connection unexpectedly closed") 370 if self.debuglevel > 0: 371 print>>stderr, 'reply:', repr(line) 372 if len(line) > _MAXLINE: 373 raise SMTPResponseException(500, "Line too long.") 374 resp.append(line[4:].strip()) 375 code = line[:3] 376 # Check that the error code is syntactically correct. 377 # Don't attempt to read a continuation line if it is broken. 378 try: 379 errcode = int(code) 380 except ValueError: 381 errcode = -1 382 break 383 # Check if multiline response. 384 if line[3:4] != "-": 385 break 386 387 errmsg = "\n".join(resp) 388 if self.debuglevel > 0: 389 print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode, errmsg) 390 return errcode, errmsg 391 392 def docmd(self, cmd, args=""): 393 """Send a command, and return its response code.""" 394 self.putcmd(cmd, args) 395 return self.getreply() 396 397 # std smtp commands 398 def helo(self, name=''): 399 """SMTP 'helo' command. 400 Hostname to send for this command defaults to the FQDN of the local 401 host. 402 """ 403 self.putcmd("helo", name or self.local_hostname) 404 (code, msg) = self.getreply() 405 self.helo_resp = msg 406 return (code, msg) 407 408 def ehlo(self, name=''): 409 """ SMTP 'ehlo' command. 410 Hostname to send for this command defaults to the FQDN of the local 411 host. 412 """ 413 self.esmtp_features = {} 414 self.putcmd(self.ehlo_msg, name or self.local_hostname) 415 (code, msg) = self.getreply() 416 # According to RFC1869 some (badly written) 417 # MTA's will disconnect on an ehlo. Toss an exception if 418 # that happens -ddm 419 if code == -1 and len(msg) == 0: 420 self.close() 421 raise SMTPServerDisconnected("Server not connected") 422 self.ehlo_resp = msg 423 if code != 250: 424 return (code, msg) 425 self.does_esmtp = 1 426 #parse the ehlo response -ddm 427 resp = self.ehlo_resp.split('\n') 428 del resp[0] 429 for each in resp: 430 # To be able to communicate with as many SMTP servers as possible, 431 # we have to take the old-style auth advertisement into account, 432 # because: 433 # 1) Else our SMTP feature parser gets confused. 434 # 2) There are some servers that only advertise the auth methods we 435 # support using the old style. 436 auth_match = OLDSTYLE_AUTH.match(each) 437 if auth_match: 438 # This doesn't remove duplicates, but that's no problem 439 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ 440 + " " + auth_match.groups(0)[0] 441 continue 442 443 # RFC 1869 requires a space between ehlo keyword and parameters. 444 # It's actually stricter, in that only spaces are allowed between 445 # parameters, but were not going to check for that here. Note 446 # that the space isn't present if there are no parameters. 447 m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each) 448 if m: 449 feature = m.group("feature").lower() 450 params = m.string[m.end("feature"):].strip() 451 if feature == "auth": 452 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ 453 + " " + params 454 else: 455 self.esmtp_features[feature] = params 456 return (code, msg) 457 458 def has_extn(self, opt): 459 """Does the server support a given SMTP service extension?""" 460 return opt.lower() in self.esmtp_features 461 462 def help(self, args=''): 463 """SMTP 'help' command. 464 Returns help text from server.""" 465 self.putcmd("help", args) 466 return self.getreply()[1] 467 468 def rset(self): 469 """SMTP 'rset' command -- resets session.""" 470 return self.docmd("rset") 471 472 def noop(self): 473 """SMTP 'noop' command -- doesn't do anything :>""" 474 return self.docmd("noop") 475 476 def mail(self, sender, options=[]): 477 """SMTP 'mail' command -- begins mail xfer session.""" 478 optionlist = '' 479 if options and self.does_esmtp: 480 optionlist = ' ' + ' '.join(options) 481 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist)) 482 return self.getreply() 483 484 def rcpt(self, recip, options=[]): 485 """SMTP 'rcpt' command -- indicates 1 recipient for this mail.""" 486 optionlist = '' 487 if options and self.does_esmtp: 488 optionlist = ' ' + ' '.join(options) 489 self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist)) 490 return self.getreply() 491 492 def data(self, msg): 493 """SMTP 'DATA' command -- sends message data to server. 494 495 Automatically quotes lines beginning with a period per rfc821. 496 Raises SMTPDataError if there is an unexpected reply to the 497 DATA command; the return value from this method is the final 498 response code received when the all data is sent. 499 """ 500 self.putcmd("data") 501 (code, repl) = self.getreply() 502 if self.debuglevel > 0: 503 print>>stderr, "data:", (code, repl) 504 if code != 354: 505 raise SMTPDataError(code, repl) 506 else: 507 q = quotedata(msg) 508 if q[-2:] != CRLF: 509 q = q + CRLF 510 q = q + "." + CRLF 511 self.send(q) 512 (code, msg) = self.getreply() 513 if self.debuglevel > 0: 514 print>>stderr, "data:", (code, msg) 515 return (code, msg) 516 517 def verify(self, address): 518 """SMTP 'verify' command -- checks for address validity.""" 519 self.putcmd("vrfy", _addr_only(address)) 520 return self.getreply() 521 # a.k.a. 522 vrfy = verify 523 524 def expn(self, address): 525 """SMTP 'expn' command -- expands a mailing list.""" 526 self.putcmd("expn", _addr_only(address)) 527 return self.getreply() 528 529 # some useful methods 530 531 def ehlo_or_helo_if_needed(self): 532 """Call self.ehlo() and/or self.helo() if needed. 533 534 If there has been no previous EHLO or HELO command this session, this 535 method tries ESMTP EHLO first. 536 537 This method may raise the following exceptions: 538 539 SMTPHeloError The server didn't reply properly to 540 the helo greeting. 541 """ 542 if self.helo_resp is None and self.ehlo_resp is None: 543 if not (200 <= self.ehlo()[0] <= 299): 544 (code, resp) = self.helo() 545 if not (200 <= code <= 299): 546 raise SMTPHeloError(code, resp) 547 548 def login(self, user, password): 549 """Log in on an SMTP server that requires authentication. 550 551 The arguments are: 552 - user: The user name to authenticate with. 553 - password: The password for the authentication. 554 555 If there has been no previous EHLO or HELO command this session, this 556 method tries ESMTP EHLO first. 557 558 This method will return normally if the authentication was successful. 559 560 This method may raise the following exceptions: 561 562 SMTPHeloError The server didn't reply properly to 563 the helo greeting. 564 SMTPAuthenticationError The server didn't accept the username/ 565 password combination. 566 SMTPException No suitable authentication method was 567 found. 568 """ 569 570 def encode_cram_md5(challenge, user, password): 571 challenge = base64.decodestring(challenge) 572 response = user + " " + hmac.HMAC(password, challenge).hexdigest() 573 return encode_base64(response, eol="") 574 575 def encode_plain(user, password): 576 return encode_base64("\0%s\0%s" % (user, password), eol="") 577 578 579 AUTH_PLAIN = "PLAIN" 580 AUTH_CRAM_MD5 = "CRAM-MD5" 581 AUTH_LOGIN = "LOGIN" 582 583 self.ehlo_or_helo_if_needed() 584 585 if not self.has_extn("auth"): 586 raise SMTPException("SMTP AUTH extension not supported by server.") 587 588 # Authentication methods the server supports: 589 authlist = self.esmtp_features["auth"].split() 590 591 # List of authentication methods we support: from preferred to 592 # less preferred methods. Except for the purpose of testing the weaker 593 # ones, we prefer stronger methods like CRAM-MD5: 594 preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN] 595 596 # Determine the authentication method we'll use 597 authmethod = None 598 for method in preferred_auths: 599 if method in authlist: 600 authmethod = method 601 break 602 603 if authmethod == AUTH_CRAM_MD5: 604 (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5) 605 if code == 503: 606 # 503 == 'Error: already authenticated' 607 return (code, resp) 608 (code, resp) = self.docmd(encode_cram_md5(resp, user, password)) 609 elif authmethod == AUTH_PLAIN: 610 (code, resp) = self.docmd("AUTH", 611 AUTH_PLAIN + " " + encode_plain(user, password)) 612 elif authmethod == AUTH_LOGIN: 613 (code, resp) = self.docmd("AUTH", 614 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol=""))) 615 if code != 334: 616 raise SMTPAuthenticationError(code, resp) 617 (code, resp) = self.docmd(encode_base64(password, eol="")) 618 elif authmethod is None: 619 raise SMTPException("No suitable authentication method found.") 620 if code not in (235, 503): 621 # 235 == 'Authentication successful' 622 # 503 == 'Error: already authenticated' 623 raise SMTPAuthenticationError(code, resp) 624 return (code, resp) 625 626 def starttls(self, keyfile=None, certfile=None): 627 """Puts the connection to the SMTP server into TLS mode. 628 629 If there has been no previous EHLO or HELO command this session, this 630 method tries ESMTP EHLO first. 631 632 If the server supports TLS, this will encrypt the rest of the SMTP 633 session. If you provide the keyfile and certfile parameters, 634 the identity of the SMTP server and client can be checked. This, 635 however, depends on whether the socket module really checks the 636 certificates. 637 638 This method may raise the following exceptions: 639 640 SMTPHeloError The server didn't reply properly to 641 the helo greeting. 642 """ 643 self.ehlo_or_helo_if_needed() 644 if not self.has_extn("starttls"): 645 raise SMTPException("STARTTLS extension not supported by server.") 646 (resp, reply) = self.docmd("STARTTLS") 647 if resp == 220: 648 if not _have_ssl: 649 raise RuntimeError("No SSL support included in this Python") 650 self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) 651 self.file = SSLFakeFile(self.sock) 652 # RFC 3207: 653 # The client MUST discard any knowledge obtained from 654 # the server, such as the list of SMTP service extensions, 655 # which was not obtained from the TLS negotiation itself. 656 self.helo_resp = None 657 self.ehlo_resp = None 658 self.esmtp_features = {} 659 self.does_esmtp = 0 660 else: 661 # RFC 3207: 662 # 501 Syntax error (no parameters allowed) 663 # 454 TLS not available due to temporary reason 664 raise SMTPResponseException(resp, reply) 665 return (resp, reply) 666 667 def sendmail(self, from_addr, to_addrs, msg, mail_options=[], 668 rcpt_options=[]): 669 """This command performs an entire mail transaction. 670 671 The arguments are: 672 - from_addr : The address sending this mail. 673 - to_addrs : A list of addresses to send this mail to. A bare 674 string will be treated as a list with 1 address. 675 - msg : The message to send. 676 - mail_options : List of ESMTP options (such as 8bitmime) for the 677 mail command. 678 - rcpt_options : List of ESMTP options (such as DSN commands) for 679 all the rcpt commands. 680 681 If there has been no previous EHLO or HELO command this session, this 682 method tries ESMTP EHLO first. If the server does ESMTP, message size 683 and each of the specified options will be passed to it. If EHLO 684 fails, HELO will be tried and ESMTP options suppressed. 685 686 This method will return normally if the mail is accepted for at least 687 one recipient. It returns a dictionary, with one entry for each 688 recipient that was refused. Each entry contains a tuple of the SMTP 689 error code and the accompanying error message sent by the server. 690 691 This method may raise the following exceptions: 692 693 SMTPHeloError The server didn't reply properly to 694 the helo greeting. 695 SMTPRecipientsRefused The server rejected ALL recipients 696 (no mail was sent). 697 SMTPSenderRefused The server didn't accept the from_addr. 698 SMTPDataError The server replied with an unexpected 699 error code (other than a refusal of 700 a recipient). 701 702 Note: the connection will be open even after an exception is raised. 703 704 Example: 705 706 >>> import smtplib 707 >>> s=smtplib.SMTP("localhost") 708 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] 709 >>> msg = '''\\ 710 ... From: Me@my.org 711 ... Subject: testin'... 712 ... 713 ... This is a test ''' 714 >>> s.sendmail("me@my.org",tolist,msg) 715 { "three@three.org" : ( 550 ,"User unknown" ) } 716 >>> s.quit() 717 718 In the above example, the message was accepted for delivery to three 719 of the four addresses, and one was rejected, with the error code 720 550. If all addresses are accepted, then the method will return an 721 empty dictionary. 722 723 """ 724 self.ehlo_or_helo_if_needed() 725 esmtp_opts = [] 726 if self.does_esmtp: 727 # Hmmm? what's this? -ddm 728 # self.esmtp_features['7bit']="" 729 if self.has_extn('size'): 730 esmtp_opts.append("size=%d" % len(msg)) 731 for option in mail_options: 732 esmtp_opts.append(option) 733 734 (code, resp) = self.mail(from_addr, esmtp_opts) 735 if code != 250: 736 self.rset() 737 raise SMTPSenderRefused(code, resp, from_addr) 738 senderrs = {} 739 if isinstance(to_addrs, basestring): 740 to_addrs = [to_addrs] 741 for each in to_addrs: 742 (code, resp) = self.rcpt(each, rcpt_options) 743 if (code != 250) and (code != 251): 744 senderrs[each] = (code, resp) 745 if len(senderrs) == len(to_addrs): 746 # the server refused all our recipients 747 self.rset() 748 raise SMTPRecipientsRefused(senderrs) 749 (code, resp) = self.data(msg) 750 if code != 250: 751 self.rset() 752 raise SMTPDataError(code, resp) 753 #if we got here then somebody got our mail 754 return senderrs 755 756 757 def close(self): 758 """Close the connection to the SMTP server.""" 759 try: 760 file = self.file 761 self.file = None 762 if file: 763 file.close() 764 finally: 765 sock = self.sock 766 self.sock = None 767 if sock: 768 sock.close() 769 770 771 def quit(self): 772 """Terminate the SMTP session.""" 773 res = self.docmd("quit") 774 # A new EHLO is required after reconnecting with connect() 775 self.ehlo_resp = self.helo_resp = None 776 self.esmtp_features = {} 777 self.does_esmtp = False 778 self.close() 779 return res 780 781if _have_ssl: 782 783 class SMTP_SSL(SMTP): 784 """ This is a subclass derived from SMTP that connects over an SSL 785 encrypted socket (to use this class you need a socket module that was 786 compiled with SSL support). If host is not specified, '' (the local 787 host) is used. If port is omitted, the standard SMTP-over-SSL port 788 (465) is used. local_hostname has the same meaning as it does in the 789 SMTP class. keyfile and certfile are also optional - they can contain 790 a PEM formatted private key and certificate chain file for the SSL 791 connection. 792 793 """ 794 795 default_port = SMTP_SSL_PORT 796 797 def __init__(self, host='', port=0, local_hostname=None, 798 keyfile=None, certfile=None, 799 timeout=socket._GLOBAL_DEFAULT_TIMEOUT): 800 self.keyfile = keyfile 801 self.certfile = certfile 802 SMTP.__init__(self, host, port, local_hostname, timeout) 803 804 def _get_socket(self, host, port, timeout): 805 if self.debuglevel > 0: 806 print>>stderr, 'connect:', (host, port) 807 new_socket = socket.create_connection((host, port), timeout) 808 new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile) 809 self.file = SSLFakeFile(new_socket) 810 return new_socket 811 812 __all__.append("SMTP_SSL") 813 814# 815# LMTP extension 816# 817LMTP_PORT = 2003 818 819class LMTP(SMTP): 820 """LMTP - Local Mail Transfer Protocol 821 822 The LMTP protocol, which is very similar to ESMTP, is heavily based 823 on the standard SMTP client. It's common to use Unix sockets for 824 LMTP, so our connect() method must support that as well as a regular 825 host:port server. local_hostname has the same meaning as it does in 826 the SMTP class. To specify a Unix socket, you must use an absolute 827 path as the host, starting with a '/'. 828 829 Authentication is supported, using the regular SMTP mechanism. When 830 using a Unix socket, LMTP generally don't support or require any 831 authentication, but your mileage might vary.""" 832 833 ehlo_msg = "lhlo" 834 835 def __init__(self, host='', port=LMTP_PORT, local_hostname=None): 836 """Initialize a new instance.""" 837 SMTP.__init__(self, host, port, local_hostname) 838 839 def connect(self, host='localhost', port=0): 840 """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" 841 if host[0] != '/': 842 return SMTP.connect(self, host, port) 843 844 # Handle Unix-domain sockets. 845 try: 846 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 847 self.sock.connect(host) 848 except socket.error: 849 if self.debuglevel > 0: 850 print>>stderr, 'connect fail:', host 851 if self.sock: 852 self.sock.close() 853 self.sock = None 854 raise 855 (code, msg) = self.getreply() 856 if self.debuglevel > 0: 857 print>>stderr, "connect:", msg 858 return (code, msg) 859 860 861# Test the sendmail method, which tests most of the others. 862# Note: This always sends to localhost. 863if __name__ == '__main__': 864 import sys 865 866 def prompt(prompt): 867 sys.stdout.write(prompt + ": ") 868 return sys.stdin.readline().strip() 869 870 fromaddr = prompt("From") 871 toaddrs = prompt("To").split(',') 872 print "Enter message, end with ^D:" 873 msg = '' 874 while 1: 875 line = sys.stdin.readline() 876 if not line: 877 break 878 msg = msg + line 879 print "Message length is %d" % len(msg) 880 881 server = SMTP('localhost') 882 server.set_debuglevel(1) 883 server.sendmail(fromaddr, toaddrs, msg) 884 server.quit() 885