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