Added new optional credentials argument to SMTPHandler.__init__, and smtp.login(...
[python.git] / Lib / smtplib.py
blobfc1df51eaf914f2131c7f46c4f324452bb23efe9
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","SMTP_SSL"]
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 class SSLFakeSocket:
132 """A fake socket object that really wraps a SSLObject.
134 It only supports what is needed in smtplib.
136 def __init__(self, realsock, sslobj):
137 self.realsock = realsock
138 self.sslobj = sslobj
140 def send(self, str):
141 self.sslobj.write(str)
142 return len(str)
144 sendall = send
146 def close(self):
147 self.realsock.close()
149 class SSLFakeFile:
150 """A fake file like object that really wraps a SSLObject.
152 It only supports what is needed in smtplib.
154 def __init__(self, sslobj):
155 self.sslobj = sslobj
157 def readline(self):
158 str = ""
159 chr = None
160 while chr != "\n":
161 chr = self.sslobj.read(1)
162 str += chr
163 return str
165 def close(self):
166 pass
168 def quoteaddr(addr):
169 """Quote a subset of the email addresses defined by RFC 821.
171 Should be able to handle anything rfc822.parseaddr can handle.
173 m = (None, None)
174 try:
175 m = email.utils.parseaddr(addr)[1]
176 except AttributeError:
177 pass
178 if m == (None, None): # Indicates parse failure or AttributeError
179 # something weird here.. punt -ddm
180 return "<%s>" % addr
181 elif m is None:
182 # the sender wants an empty return address
183 return "<>"
184 else:
185 return "<%s>" % m
187 def quotedata(data):
188 """Quote data for email.
190 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
191 Internet CRLF end-of-line.
193 return re.sub(r'(?m)^\.', '..',
194 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
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.
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.
209 does_esmtp
210 This is a True value _after you do an EHLO command_, if the
211 server supports ESMTP.
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).
219 Note, all extension names are mapped to lower case in the
220 dictionary.
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.
226 debuglevel = 0
227 file = None
228 helo_resp = None
229 ehlo_msg = "ehlo"
230 ehlo_resp = None
231 does_esmtp = 0
233 def __init__(self, host='', port=0, local_hostname=None, timeout=None):
234 """Initialize a new instance.
236 If specified, `host' is the name of the remote host to which to
237 connect. If specified, `port' specifies the port to which to connect.
238 By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised
239 if the specified `host' doesn't respond correctly. If specified,
240 `local_hostname` is used as the FQDN of the local host. By default,
241 the local hostname is found using socket.getfqdn().
244 self.timeout = timeout
245 self.esmtp_features = {}
246 self.default_port = SMTP_PORT
247 if host:
248 (code, msg) = self.connect(host, port)
249 if code != 220:
250 raise SMTPConnectError(code, msg)
251 if local_hostname is not None:
252 self.local_hostname = local_hostname
253 else:
254 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
255 # if that can't be calculated, that we should use a domain literal
256 # instead (essentially an encoded IP address like [A.B.C.D]).
257 fqdn = socket.getfqdn()
258 if '.' in fqdn:
259 self.local_hostname = fqdn
260 else:
261 # We can't find an fqdn hostname, so use a domain literal
262 addr = '127.0.0.1'
263 try:
264 addr = socket.gethostbyname(socket.gethostname())
265 except socket.gaierror:
266 pass
267 self.local_hostname = '[%s]' % addr
269 def set_debuglevel(self, debuglevel):
270 """Set the debug output level.
272 A non-false value results in debug messages for connection and for all
273 messages sent to and received from the server.
276 self.debuglevel = debuglevel
278 def _get_socket(self, port, host, timeout):
279 # This makes it simpler for SMTP_SSL to use the SMTP connect code
280 # and just alter the socket connection bit.
281 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
282 return socket.create_connection((port, host), timeout)
284 def connect(self, host='localhost', port = 0):
285 """Connect to a host on a given port.
287 If the hostname ends with a colon (`:') followed by a number, and
288 there is no port specified, that suffix will be stripped off and the
289 number interpreted as the port number to use.
291 Note: This method is automatically invoked by __init__, if a host is
292 specified during instantiation.
295 if not port and (host.find(':') == host.rfind(':')):
296 i = host.rfind(':')
297 if i >= 0:
298 host, port = host[:i], host[i+1:]
299 try: port = int(port)
300 except ValueError:
301 raise socket.error, "nonnumeric port"
302 if not port: port = self.default_port
303 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
304 self.sock = self._get_socket(host, port, self.timeout)
305 (code, msg) = self.getreply()
306 if self.debuglevel > 0: print>>stderr, "connect:", msg
307 return (code, msg)
309 def send(self, str):
310 """Send `str' to the server."""
311 if self.debuglevel > 0: print>>stderr, 'send:', repr(str)
312 if 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')
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)
329 def getreply(self):
330 """Get a reply from the server.
332 Returns a tuple consisting of:
334 - server response code (e.g. '250', or such, if all goes well)
335 Note: returns -1 if it can't read response code.
337 - server response string corresponding to response code (multiline
338 responses are converted to a single, multiline string).
340 Raises SMTPServerDisconnected if end-of-file is reached.
342 resp=[]
343 if self.file is None:
344 self.file = self.sock.makefile('rb')
345 while 1:
346 line = self.file.readline()
347 if line == '':
348 self.close()
349 raise SMTPServerDisconnected("Connection unexpectedly closed")
350 if self.debuglevel > 0: print>>stderr, 'reply:', repr(line)
351 resp.append(line[4:].strip())
352 code=line[:3]
353 # Check that the error code is syntactically correct.
354 # Don't attempt to read a continuation line if it is broken.
355 try:
356 errcode = int(code)
357 except ValueError:
358 errcode = -1
359 break
360 # Check if multiline response.
361 if line[3:4]!="-":
362 break
364 errmsg = "\n".join(resp)
365 if self.debuglevel > 0:
366 print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
367 return errcode, errmsg
369 def docmd(self, cmd, args=""):
370 """Send a command, and return its response code."""
371 self.putcmd(cmd,args)
372 return self.getreply()
374 # std smtp commands
375 def helo(self, name=''):
376 """SMTP 'helo' command.
377 Hostname to send for this command defaults to the FQDN of the local
378 host.
380 self.putcmd("helo", name or self.local_hostname)
381 (code,msg)=self.getreply()
382 self.helo_resp=msg
383 return (code,msg)
385 def ehlo(self, name=''):
386 """ SMTP 'ehlo' command.
387 Hostname to send for this command defaults to the FQDN of the local
388 host.
390 self.esmtp_features = {}
391 self.putcmd(self.ehlo_msg, name or self.local_hostname)
392 (code,msg)=self.getreply()
393 # According to RFC1869 some (badly written)
394 # MTA's will disconnect on an ehlo. Toss an exception if
395 # that happens -ddm
396 if code == -1 and len(msg) == 0:
397 self.close()
398 raise SMTPServerDisconnected("Server not connected")
399 self.ehlo_resp=msg
400 if code != 250:
401 return (code,msg)
402 self.does_esmtp=1
403 #parse the ehlo response -ddm
404 resp=self.ehlo_resp.split('\n')
405 del resp[0]
406 for each in resp:
407 # To be able to communicate with as many SMTP servers as possible,
408 # we have to take the old-style auth advertisement into account,
409 # because:
410 # 1) Else our SMTP feature parser gets confused.
411 # 2) There are some servers that only advertise the auth methods we
412 # support using the old style.
413 auth_match = OLDSTYLE_AUTH.match(each)
414 if auth_match:
415 # This doesn't remove duplicates, but that's no problem
416 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
417 + " " + auth_match.groups(0)[0]
418 continue
420 # RFC 1869 requires a space between ehlo keyword and parameters.
421 # It's actually stricter, in that only spaces are allowed between
422 # parameters, but were not going to check for that here. Note
423 # that the space isn't present if there are no parameters.
424 m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?',each)
425 if m:
426 feature=m.group("feature").lower()
427 params=m.string[m.end("feature"):].strip()
428 if feature == "auth":
429 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
430 + " " + params
431 else:
432 self.esmtp_features[feature]=params
433 return (code,msg)
435 def has_extn(self, opt):
436 """Does the server support a given SMTP service extension?"""
437 return opt.lower() in self.esmtp_features
439 def help(self, args=''):
440 """SMTP 'help' command.
441 Returns help text from server."""
442 self.putcmd("help", args)
443 return self.getreply()[1]
445 def rset(self):
446 """SMTP 'rset' command -- resets session."""
447 return self.docmd("rset")
449 def noop(self):
450 """SMTP 'noop' command -- doesn't do anything :>"""
451 return self.docmd("noop")
453 def mail(self,sender,options=[]):
454 """SMTP 'mail' command -- begins mail xfer session."""
455 optionlist = ''
456 if options and self.does_esmtp:
457 optionlist = ' ' + ' '.join(options)
458 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
459 return self.getreply()
461 def rcpt(self,recip,options=[]):
462 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
463 optionlist = ''
464 if options and self.does_esmtp:
465 optionlist = ' ' + ' '.join(options)
466 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
467 return self.getreply()
469 def data(self,msg):
470 """SMTP 'DATA' command -- sends message data to server.
472 Automatically quotes lines beginning with a period per rfc821.
473 Raises SMTPDataError if there is an unexpected reply to the
474 DATA command; the return value from this method is the final
475 response code received when the all data is sent.
477 self.putcmd("data")
478 (code,repl)=self.getreply()
479 if self.debuglevel >0 : print>>stderr, "data:", (code,repl)
480 if code != 354:
481 raise SMTPDataError(code,repl)
482 else:
483 q = quotedata(msg)
484 if q[-2:] != CRLF:
485 q = q + CRLF
486 q = q + "." + CRLF
487 self.send(q)
488 (code,msg)=self.getreply()
489 if self.debuglevel >0 : print>>stderr, "data:", (code,msg)
490 return (code,msg)
492 def verify(self, address):
493 """SMTP 'verify' command -- checks for address validity."""
494 self.putcmd("vrfy", quoteaddr(address))
495 return self.getreply()
496 # a.k.a.
497 vrfy=verify
499 def expn(self, address):
500 """SMTP 'verify' command -- checks for address validity."""
501 self.putcmd("expn", quoteaddr(address))
502 return self.getreply()
504 # some useful methods
506 def login(self, user, password):
507 """Log in on an SMTP server that requires authentication.
509 The arguments are:
510 - user: The user name to authenticate with.
511 - password: The password for the authentication.
513 If there has been no previous EHLO or HELO command this session, this
514 method tries ESMTP EHLO first.
516 This method will return normally if the authentication was successful.
518 This method may raise the following exceptions:
520 SMTPHeloError The server didn't reply properly to
521 the helo greeting.
522 SMTPAuthenticationError The server didn't accept the username/
523 password combination.
524 SMTPException No suitable authentication method was
525 found.
528 def encode_cram_md5(challenge, user, password):
529 challenge = base64.decodestring(challenge)
530 response = user + " " + hmac.HMAC(password, challenge).hexdigest()
531 return encode_base64(response, eol="")
533 def encode_plain(user, password):
534 return encode_base64("\0%s\0%s" % (user, password), eol="")
537 AUTH_PLAIN = "PLAIN"
538 AUTH_CRAM_MD5 = "CRAM-MD5"
539 AUTH_LOGIN = "LOGIN"
541 if self.helo_resp is None and self.ehlo_resp is None:
542 if not (200 <= self.ehlo()[0] <= 299):
543 (code, resp) = self.helo()
544 if not (200 <= code <= 299):
545 raise SMTPHeloError(code, resp)
547 if not self.has_extn("auth"):
548 raise SMTPException("SMTP AUTH extension not supported by server.")
550 # Authentication methods the server supports:
551 authlist = self.esmtp_features["auth"].split()
553 # List of authentication methods we support: from preferred to
554 # less preferred methods. Except for the purpose of testing the weaker
555 # ones, we prefer stronger methods like CRAM-MD5:
556 preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
558 # Determine the authentication method we'll use
559 authmethod = None
560 for method in preferred_auths:
561 if method in authlist:
562 authmethod = method
563 break
565 if authmethod == AUTH_CRAM_MD5:
566 (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
567 if code == 503:
568 # 503 == 'Error: already authenticated'
569 return (code, resp)
570 (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
571 elif authmethod == AUTH_PLAIN:
572 (code, resp) = self.docmd("AUTH",
573 AUTH_PLAIN + " " + encode_plain(user, password))
574 elif authmethod == AUTH_LOGIN:
575 (code, resp) = self.docmd("AUTH",
576 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
577 if code != 334:
578 raise SMTPAuthenticationError(code, resp)
579 (code, resp) = self.docmd(encode_base64(password, eol=""))
580 elif authmethod is None:
581 raise SMTPException("No suitable authentication method found.")
582 if code not in (235, 503):
583 # 235 == 'Authentication successful'
584 # 503 == 'Error: already authenticated'
585 raise SMTPAuthenticationError(code, resp)
586 return (code, resp)
588 def starttls(self, keyfile = None, certfile = None):
589 """Puts the connection to the SMTP server into TLS mode.
591 If the server supports TLS, this will encrypt the rest of the SMTP
592 session. If you provide the keyfile and certfile parameters,
593 the identity of the SMTP server and client can be checked. This,
594 however, depends on whether the socket module really checks the
595 certificates.
597 (resp, reply) = self.docmd("STARTTLS")
598 if resp == 220:
599 sslobj = socket.ssl(self.sock, keyfile, certfile)
600 self.sock = SSLFakeSocket(self.sock, sslobj)
601 self.file = SSLFakeFile(sslobj)
602 return (resp, reply)
604 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
605 rcpt_options=[]):
606 """This command performs an entire mail transaction.
608 The arguments are:
609 - from_addr : The address sending this mail.
610 - to_addrs : A list of addresses to send this mail to. A bare
611 string will be treated as a list with 1 address.
612 - msg : The message to send.
613 - mail_options : List of ESMTP options (such as 8bitmime) for the
614 mail command.
615 - rcpt_options : List of ESMTP options (such as DSN commands) for
616 all the rcpt commands.
618 If there has been no previous EHLO or HELO command this session, this
619 method tries ESMTP EHLO first. If the server does ESMTP, message size
620 and each of the specified options will be passed to it. If EHLO
621 fails, HELO will be tried and ESMTP options suppressed.
623 This method will return normally if the mail is accepted for at least
624 one recipient. It returns a dictionary, with one entry for each
625 recipient that was refused. Each entry contains a tuple of the SMTP
626 error code and the accompanying error message sent by the server.
628 This method may raise the following exceptions:
630 SMTPHeloError The server didn't reply properly to
631 the helo greeting.
632 SMTPRecipientsRefused The server rejected ALL recipients
633 (no mail was sent).
634 SMTPSenderRefused The server didn't accept the from_addr.
635 SMTPDataError The server replied with an unexpected
636 error code (other than a refusal of
637 a recipient).
639 Note: the connection will be open even after an exception is raised.
641 Example:
643 >>> import smtplib
644 >>> s=smtplib.SMTP("localhost")
645 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
646 >>> msg = '''\\
647 ... From: Me@my.org
648 ... Subject: testin'...
650 ... This is a test '''
651 >>> s.sendmail("me@my.org",tolist,msg)
652 { "three@three.org" : ( 550 ,"User unknown" ) }
653 >>> s.quit()
655 In the above example, the message was accepted for delivery to three
656 of the four addresses, and one was rejected, with the error code
657 550. If all addresses are accepted, then the method will return an
658 empty dictionary.
661 if self.helo_resp is None and self.ehlo_resp is None:
662 if not (200 <= self.ehlo()[0] <= 299):
663 (code,resp) = self.helo()
664 if not (200 <= code <= 299):
665 raise SMTPHeloError(code, resp)
666 esmtp_opts = []
667 if self.does_esmtp:
668 # Hmmm? what's this? -ddm
669 # self.esmtp_features['7bit']=""
670 if self.has_extn('size'):
671 esmtp_opts.append("size=%d" % len(msg))
672 for option in mail_options:
673 esmtp_opts.append(option)
675 (code,resp) = self.mail(from_addr, esmtp_opts)
676 if code != 250:
677 self.rset()
678 raise SMTPSenderRefused(code, resp, from_addr)
679 senderrs={}
680 if isinstance(to_addrs, basestring):
681 to_addrs = [to_addrs]
682 for each in to_addrs:
683 (code,resp)=self.rcpt(each, rcpt_options)
684 if (code != 250) and (code != 251):
685 senderrs[each]=(code,resp)
686 if len(senderrs)==len(to_addrs):
687 # the server refused all our recipients
688 self.rset()
689 raise SMTPRecipientsRefused(senderrs)
690 (code,resp) = self.data(msg)
691 if code != 250:
692 self.rset()
693 raise SMTPDataError(code, resp)
694 #if we got here then somebody got our mail
695 return senderrs
698 def close(self):
699 """Close the connection to the SMTP server."""
700 if self.file:
701 self.file.close()
702 self.file = None
703 if self.sock:
704 self.sock.close()
705 self.sock = None
708 def quit(self):
709 """Terminate the SMTP session."""
710 self.docmd("quit")
711 self.close()
713 class SMTP_SSL(SMTP):
714 """ This is a subclass derived from SMTP that connects over an SSL encrypted
715 socket (to use this class you need a socket module that was compiled with SSL
716 support). If host is not specified, '' (the local host) is used. If port is
717 omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile
718 are also optional - they can contain a PEM formatted private key and
719 certificate chain file for the SSL connection.
721 def __init__(self, host='', port=0, local_hostname=None,
722 keyfile=None, certfile=None, timeout=None):
723 self.keyfile = keyfile
724 self.certfile = certfile
725 SMTP.__init__(self, host, port, local_hostname, timeout)
726 self.default_port = SMTP_SSL_PORT
728 def _get_socket(self, host, port, timeout):
729 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
730 self.sock = socket.create_connection((host, port), timeout)
731 sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
732 self.sock = SSLFakeSocket(self.sock, sslobj)
733 self.file = SSLFakeFile(sslobj)
736 # LMTP extension
738 LMTP_PORT = 2003
740 class LMTP(SMTP):
741 """LMTP - Local Mail Transfer Protocol
743 The LMTP protocol, which is very similar to ESMTP, is heavily based
744 on the standard SMTP client. It's common to use Unix sockets for LMTP,
745 so our connect() method must support that as well as a regular
746 host:port server. To specify a Unix socket, you must use an absolute
747 path as the host, starting with a '/'.
749 Authentication is supported, using the regular SMTP mechanism. When
750 using a Unix socket, LMTP generally don't support or require any
751 authentication, but your mileage might vary."""
753 ehlo_msg = "lhlo"
755 def __init__(self, host = '', port = LMTP_PORT, local_hostname = None):
756 """Initialize a new instance."""
757 SMTP.__init__(self, host, port, local_hostname)
759 def connect(self, host = 'localhost', port = 0):
760 """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
761 if host[0] != '/':
762 return SMTP.connect(self, host, port)
764 # Handle Unix-domain sockets.
765 try:
766 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
767 self.sock.connect(host)
768 except socket.error, msg:
769 if self.debuglevel > 0: print>>stderr, 'connect fail:', host
770 if self.sock:
771 self.sock.close()
772 self.sock = None
773 raise socket.error, msg
774 (code, msg) = self.getreply()
775 if self.debuglevel > 0: print>>stderr, "connect:", msg
776 return (code, msg)
779 # Test the sendmail method, which tests most of the others.
780 # Note: This always sends to localhost.
781 if __name__ == '__main__':
782 import sys
784 def prompt(prompt):
785 sys.stdout.write(prompt + ": ")
786 return sys.stdin.readline().strip()
788 fromaddr = prompt("From")
789 toaddrs = prompt("To").split(',')
790 print "Enter message, end with ^D:"
791 msg = ''
792 while 1:
793 line = sys.stdin.readline()
794 if not line:
795 break
796 msg = msg + line
797 print "Message length is %d" % len(msg)
799 server = SMTP('localhost')
800 server.set_debuglevel(1)
801 server.sendmail(fromaddr, toaddrs, msg)
802 server.quit()