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