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