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