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