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