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).
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
17 >>> s=smtplib.SMTP("localhost")
19 This is Sendmail version 8.8.4
21 HELO EHLO MAIL RCPT DATA
22 RSET NOOP QUIT HELP VRFY
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.
29 >>> s.putcmd("vrfy","someone@here")
31 (250, "Somebody OverHere <somebody@here.my.org>")
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.
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"]
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.
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.
84 def __init__(self
, code
, 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.
96 def __init__(self
, code
, msg
, 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.
132 """Quote a subset of the email addresses defined by RFC 821.
134 Should be able to handle anything rfc822.parseaddr can handle.
138 m
= email
.utils
.parseaddr(addr
)[1]
139 except AttributeError:
141 if m
== (None, None): # Indicates parse failure or AttributeError
142 # something weird here.. punt -ddm
145 # the sender wants an empty return address
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
))
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
):
177 chr = self
.sslobj
.read(1)
188 """This class manages a connection to an SMTP or ESMTP server.
190 SMTP objects have the following attributes:
192 This is the message given by the server in response to the
193 most recent HELO command.
196 This is the message given by the server in response to the
197 most recent EHLO command. This is usually multiline.
200 This is a True value _after you do an EHLO command_, if the
201 server supports ESMTP.
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
209 Note, all extension names are mapped to lower case in the
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.
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
238 (code
, msg
) = self
.connect(host
, port
)
240 raise SMTPConnectError(code
, msg
)
241 if local_hostname
is not None:
242 self
.local_hostname
= local_hostname
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()
249 self
.local_hostname
= fqdn
251 # We can't find an fqdn hostname, so use a domain literal
254 addr
= socket
.gethostbyname(socket
.gethostname())
255 except socket
.gaierror
:
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(':')):
288 host
, port
= host
[:i
], host
[i
+1:]
289 try: port
= int(port
)
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
300 """Send `str' to the server."""
301 if self
.debuglevel
> 0: print>>stderr
, 'send:', repr(str)
302 if hasattr(self
, 'sock') and self
.sock
:
304 self
.sock
.sendall(str)
307 raise SMTPServerDisconnected('Server not connected')
309 raise SMTPServerDisconnected('please run connect() first')
311 def putcmd(self
, cmd
, args
=""):
312 """Send a command to the server."""
314 str = '%s%s' % (cmd
, CRLF
)
316 str = '%s %s%s' % (cmd
, args
, CRLF
)
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.
333 if self
.file is None:
334 self
.file = self
.sock
.makefile('rb')
336 line
= self
.file.readline()
339 raise SMTPServerDisconnected("Connection unexpectedly closed")
340 if self
.debuglevel
> 0: print>>stderr
, 'reply:', repr(line
)
341 resp
.append(line
[4:].strip())
343 # Check that the error code is syntactically correct.
344 # Don't attempt to read a continuation line if it is broken.
350 # Check if multiline response.
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()
365 def helo(self
, name
=''):
366 """SMTP 'helo' command.
367 Hostname to send for this command defaults to the FQDN of the local
370 self
.putcmd("helo", name
or self
.local_hostname
)
371 (code
,msg
)=self
.getreply()
375 def ehlo(self
, name
=''):
376 """ SMTP 'ehlo' command.
377 Hostname to send for this command defaults to the FQDN of the local
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
386 if code
== -1 and len(msg
) == 0:
388 raise SMTPServerDisconnected("Server not connected")
393 #parse the ehlo response -ddm
394 resp
=self
.ehlo_resp
.split('\n')
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,
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
)
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]
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
)
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
, "") \
422 self
.esmtp_features
[feature
]=params
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]
436 """SMTP 'rset' command -- resets session."""
437 return self
.docmd("rset")
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."""
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."""
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()
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.
468 (code
,repl
)=self
.getreply()
469 if self
.debuglevel
>0 : print>>stderr
, "data:", (code
,repl
)
471 raise SMTPDataError(code
,repl
)
478 (code
,msg
)=self
.getreply()
479 if self
.debuglevel
>0 : print>>stderr
, "data:", (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()
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
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.
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
529 SMTPAuthenticationError The server didn't accept the username/
530 password combination.
531 SMTPException No suitable authentication method was
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
="")
545 AUTH_CRAM_MD5
= "CRAM-MD5"
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
563 for method
in preferred_auths
:
564 if method
in authlist
:
568 if authmethod
== AUTH_CRAM_MD5
:
569 (code
, resp
) = self
.docmd("AUTH", AUTH_CRAM_MD5
)
571 # 503 == 'Error: already authenticated'
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
="")))
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
)
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
603 This method may raise the following exceptions:
605 SMTPHeloError The server didn't reply properly to
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")
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
)
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
= {}
627 def sendmail(self
, from_addr
, to_addrs
, msg
, mail_options
=[],
629 """This command performs an entire mail transaction.
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
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
655 SMTPRecipientsRefused The server rejected ALL recipients
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
662 Note: the connection will be open even after an exception is raised.
667 >>> s=smtplib.SMTP("localhost")
668 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
671 ... Subject: testin'...
673 ... This is a test '''
674 >>> s.sendmail("me@my.org",tolist,msg)
675 { "three@three.org" : ( 550 ,"User unknown" ) }
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
684 self
.ehlo_or_helo_if_needed()
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
)
697 raise SMTPSenderRefused(code
, resp
, from_addr
)
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
708 raise SMTPRecipientsRefused(senderrs
)
709 (code
,resp
) = self
.data(msg
)
712 raise SMTPDataError(code
, resp
)
713 #if we got here then somebody got our mail
718 """Close the connection to the SMTP server."""
728 """Terminate the SMTP session."""
729 res
= self
.docmd("quit")
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")
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."""
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."""
785 return SMTP
.connect(self
, host
, port
)
787 # Handle Unix-domain sockets.
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
796 raise socket
.error
, msg
797 (code
, msg
) = self
.getreply()
798 if self
.debuglevel
> 0: print>>stderr
, "connect:", msg
802 # Test the sendmail method, which tests most of the others.
803 # Note: This always sends to localhost.
804 if __name__
== '__main__':
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:"
816 line
= sys
.stdin
.readline()
820 print "Message length is %d" % len(msg
)
822 server
= SMTP('localhost')
823 server
.set_debuglevel(1)
824 server
.sendmail(fromaddr
, toaddrs
, msg
)