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.
50 __all__
= ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
51 "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError",
52 "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError",
53 "quoteaddr","quotedata","SMTP"]
58 # Exception classes used by this module.
59 class SMTPException(Exception):
60 """Base class for all exceptions raised by this module."""
62 class SMTPServerDisconnected(SMTPException
):
63 """Not connected to any SMTP server.
65 This exception is raised when the server unexpectedly disconnects,
66 or when an attempt is made to use the SMTP instance before
67 connecting it to a server.
70 class SMTPResponseException(SMTPException
):
71 """Base class for all exceptions that include an SMTP error code.
73 These exceptions are generated in some instances when the SMTP
74 server returns an error code. The error code is stored in the
75 `smtp_code' attribute of the error, and the `smtp_error' attribute
76 is set to the error message.
79 def __init__(self
, code
, msg
):
82 self
.args
= (code
, msg
)
84 class SMTPSenderRefused(SMTPResponseException
):
85 """Sender address refused.
87 In addition to the attributes set by on all SMTPResponseException
88 exceptions, this sets `sender' to the string that the SMTP refused.
91 def __init__(self
, code
, msg
, sender
):
95 self
.args
= (code
, msg
, sender
)
97 class SMTPRecipientsRefused(SMTPException
):
98 """All recipient addresses refused.
100 The errors for each recipient are accessible through the attribute
101 'recipients', which is a dictionary of exactly the same sort as
102 SMTP.sendmail() returns.
105 def __init__(self
, recipients
):
106 self
.recipients
= recipients
107 self
.args
= ( recipients
,)
110 class SMTPDataError(SMTPResponseException
):
111 """The SMTP server didn't accept the data."""
113 class SMTPConnectError(SMTPResponseException
):
114 """Error during connection establishment."""
116 class SMTPHeloError(SMTPResponseException
):
117 """The server refused our HELO reply."""
119 class SMTPAuthenticationError(SMTPResponseException
):
120 """Authentication error.
122 Most probably the server didn't accept the username/password
123 combination provided.
127 """A fake socket object that really wraps a SSLObject.
129 It only supports what is needed in smtplib.
131 def __init__(self
, realsock
, sslobj
):
132 self
.realsock
= realsock
136 self
.sslobj
.write(str)
142 self
.realsock
.close()
145 """A fake file like object that really wraps a SSLObject.
147 It only supports what is needed in smtplib.
149 def __init__( self
, sslobj
):
156 chr = self
.sslobj
.read(1)
164 """Quote a subset of the email addresses defined by RFC 821.
166 Should be able to handle anything rfc822.parseaddr can handle.
170 m
=rfc822
.parseaddr(addr
)[1]
171 except AttributeError:
174 #something weird here.. punt -ddm
180 """Quote data for email.
182 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
183 Internet CRLF end-of-line.
185 return re
.sub(r
'(?m)^\.', '..',
186 re
.sub(r
'(?:\r\n|\n|\r(?!\n))', CRLF
, data
))
190 """This class manages a connection to an SMTP or ESMTP server.
192 SMTP objects have the following attributes:
194 This is the message given by the server in response to the
195 most recent HELO command.
198 This is the message given by the server in response to the
199 most recent EHLO command. This is usually multiline.
202 This is a True value _after you do an EHLO command_, if the
203 server supports ESMTP.
206 This is a dictionary, which, if the server supports ESMTP,
207 will _after you do an EHLO command_, contain the names of the
208 SMTP service extensions this server supports, and their
211 Note, all extension names are mapped to lower case in the
214 See each method's docstrings for details. In general, there is a
215 method of the same name to perform each SMTP command. There is also a
216 method called 'sendmail' that will do an entire mail transaction.
224 def __init__(self
, host
= '', port
= 0, local_hostname
= None):
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
.esmtp_features
= {}
237 (code
, msg
) = self
.connect(host
, port
)
239 raise SMTPConnectError(code
, msg
)
240 if local_hostname
is not None:
241 self
.local_hostname
= local_hostname
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()
248 self
.local_hostname
= fqdn
250 # We can't find an fqdn hostname, so use a domain literal
251 addr
= socket
.gethostbyname(socket
.gethostname())
252 self
.local_hostname
= '[%s]' % addr
254 def set_debuglevel(self
, debuglevel
):
255 """Set the debug output level.
257 A non-false value results in debug messages for connection and for all
258 messages sent to and received from the server.
261 self
.debuglevel
= debuglevel
263 def connect(self
, host
='localhost', port
= 0):
264 """Connect to a host on a given port.
266 If the hostname ends with a colon (`:') followed by a number, and
267 there is no port specified, that suffix will be stripped off and the
268 number interpreted as the port number to use.
270 Note: This method is automatically invoked by __init__, if a host is
271 specified during instantiation.
274 if not port
and (host
.find(':') == host
.rfind(':')):
277 host
, port
= host
[:i
], host
[i
+1:]
278 try: port
= int(port
)
280 raise socket
.error
, "nonnumeric port"
281 if not port
: port
= SMTP_PORT
282 if self
.debuglevel
> 0: print 'connect:', (host
, port
)
283 msg
= "getaddrinfo returns an empty list"
285 for res
in socket
.getaddrinfo(host
, port
, 0, socket
.SOCK_STREAM
):
286 af
, socktype
, proto
, canonname
, sa
= res
288 self
.sock
= socket
.socket(af
, socktype
, proto
)
289 if self
.debuglevel
> 0: print 'connect:', (host
, port
)
290 self
.sock
.connect(sa
)
291 except socket
.error
, msg
:
292 if self
.debuglevel
> 0: print 'connect fail:', (host
, port
)
299 raise socket
.error
, msg
300 (code
, msg
) = self
.getreply()
301 if self
.debuglevel
> 0: print "connect:", msg
305 """Send `str' to the server."""
306 if self
.debuglevel
> 0: print 'send:', `
str`
309 self
.sock
.sendall(str)
312 raise SMTPServerDisconnected('Server not connected')
314 raise SMTPServerDisconnected('please run connect() first')
316 def putcmd(self
, cmd
, args
=""):
317 """Send a command to the server."""
319 str = '%s%s' % (cmd
, CRLF
)
321 str = '%s %s%s' % (cmd
, args
, CRLF
)
325 """Get a reply from the server.
327 Returns a tuple consisting of:
329 - server response code (e.g. '250', or such, if all goes well)
330 Note: returns -1 if it can't read response code.
332 - server response string corresponding to response code (multiline
333 responses are converted to a single, multiline string).
335 Raises SMTPServerDisconnected if end-of-file is reached.
338 if self
.file is None:
339 self
.file = self
.sock
.makefile('rb')
341 line
= self
.file.readline()
344 raise SMTPServerDisconnected("Connection unexpectedly closed")
345 if self
.debuglevel
> 0: print 'reply:', `line`
346 resp
.append(line
[4:].strip())
348 # Check that the error code is syntactically correct.
349 # Don't attempt to read a continuation line if it is broken.
355 # Check if multiline response.
359 errmsg
= "\n".join(resp
)
360 if self
.debuglevel
> 0:
361 print 'reply: retcode (%s); Msg: %s' % (errcode
,errmsg
)
362 return errcode
, errmsg
364 def docmd(self
, cmd
, args
=""):
365 """Send a command, and return its response code."""
366 self
.putcmd(cmd
,args
)
367 return self
.getreply()
370 def helo(self
, name
=''):
371 """SMTP 'helo' command.
372 Hostname to send for this command defaults to the FQDN of the local
375 self
.putcmd("helo", name
or self
.local_hostname
)
376 (code
,msg
)=self
.getreply()
380 def ehlo(self
, name
=''):
381 """ SMTP 'ehlo' command.
382 Hostname to send for this command defaults to the FQDN of the local
385 self
.esmtp_features
= {}
386 self
.putcmd("ehlo", name
or self
.local_hostname
)
387 (code
,msg
)=self
.getreply()
388 # According to RFC1869 some (badly written)
389 # MTA's will disconnect on an ehlo. Toss an exception if
391 if code
== -1 and len(msg
) == 0:
393 raise SMTPServerDisconnected("Server not connected")
398 #parse the ehlo response -ddm
399 resp
=self
.ehlo_resp
.split('\n')
402 # RFC 1869 requires a space between ehlo keyword and parameters.
403 # It's actually stricter, in that only spaces are allowed between
404 # parameters, but were not going to check for that here. Note
405 # that the space isn't present if there are no parameters.
406 m
=re
.match(r
'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?',each
)
408 feature
=m
.group("feature").lower()
409 params
=m
.string
[m
.end("feature"):].strip()
410 self
.esmtp_features
[feature
]=params
413 def has_extn(self
, opt
):
414 """Does the server support a given SMTP service extension?"""
415 return opt
.lower() in self
.esmtp_features
417 def help(self
, args
=''):
418 """SMTP 'help' command.
419 Returns help text from server."""
420 self
.putcmd("help", args
)
421 return self
.getreply()
424 """SMTP 'rset' command -- resets session."""
425 return self
.docmd("rset")
428 """SMTP 'noop' command -- doesn't do anything :>"""
429 return self
.docmd("noop")
431 def mail(self
,sender
,options
=[]):
432 """SMTP 'mail' command -- begins mail xfer session."""
434 if options
and self
.does_esmtp
:
435 optionlist
= ' ' + ' '.join(options
)
436 self
.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender
) ,optionlist
))
437 return self
.getreply()
439 def rcpt(self
,recip
,options
=[]):
440 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
442 if options
and self
.does_esmtp
:
443 optionlist
= ' ' + ' '.join(options
)
444 self
.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip
),optionlist
))
445 return self
.getreply()
448 """SMTP 'DATA' command -- sends message data to server.
450 Automatically quotes lines beginning with a period per rfc821.
451 Raises SMTPDataError if there is an unexpected reply to the
452 DATA command; the return value from this method is the final
453 response code received when the all data is sent.
456 (code
,repl
)=self
.getreply()
457 if self
.debuglevel
>0 : print "data:", (code
,repl
)
459 raise SMTPDataError(code
,repl
)
466 (code
,msg
)=self
.getreply()
467 if self
.debuglevel
>0 : print "data:", (code
,msg
)
470 def verify(self
, address
):
471 """SMTP 'verify' command -- checks for address validity."""
472 self
.putcmd("vrfy", quoteaddr(address
))
473 return self
.getreply()
477 def expn(self
, address
):
478 """SMTP 'verify' command -- checks for address validity."""
479 self
.putcmd("expn", quoteaddr(address
))
480 return self
.getreply()
482 # some useful methods
484 def login(self
, user
, password
):
485 """Log in on an SMTP server that requires authentication.
488 - user: The user name to authenticate with.
489 - password: The password for the authentication.
491 If there has been no previous EHLO or HELO command this session, this
492 method tries ESMTP EHLO first.
494 This method will return normally if the authentication was successful.
496 This method may raise the following exceptions:
498 SMTPHeloError The server didn't reply properly to
500 SMTPAuthenticationError The server didn't accept the username/
501 password combination.
502 SMTPException No suitable authentication method was
506 def encode_cram_md5(challenge
, user
, password
):
507 challenge
= base64
.decodestring(challenge
)
508 response
= user
+ " " + hmac
.HMAC(password
, challenge
).hexdigest()
509 return base64
.encodestring(response
)[:-1]
511 def encode_plain(user
, password
):
512 return base64
.encodestring("%s\0%s\0%s" %
513 (user
, user
, password
))[:-1]
516 AUTH_CRAM_MD5
= "CRAM-MD5"
518 if self
.helo_resp
is None and self
.ehlo_resp
is None:
519 if not (200 <= self
.ehlo()[0] <= 299):
520 (code
, resp
) = self
.helo()
521 if not (200 <= code
<= 299):
522 raise SMTPHeloError(code
, resp
)
524 if not self
.has_extn("auth"):
525 raise SMTPException("SMTP AUTH extension not supported by server.")
527 # Authentication methods the server supports:
528 authlist
= self
.esmtp_features
["auth"].split()
530 # List of authentication methods we support: from preferred to
531 # less preferred methods. Except for the purpose of testing the weaker
532 # ones, we prefer stronger methods like CRAM-MD5:
533 preferred_auths
= [AUTH_CRAM_MD5
, AUTH_PLAIN
]
534 #preferred_auths = [AUTH_PLAIN, AUTH_CRAM_MD5]
536 # Determine the authentication method we'll use
538 for method
in preferred_auths
:
539 if method
in authlist
:
542 if self
.debuglevel
> 0: print "AuthMethod:", authmethod
544 if authmethod
== AUTH_CRAM_MD5
:
545 (code
, resp
) = self
.docmd("AUTH", AUTH_CRAM_MD5
)
547 # 503 == 'Error: already authenticated'
549 (code
, resp
) = self
.docmd(encode_cram_md5(resp
, user
, password
))
550 elif authmethod
== AUTH_PLAIN
:
551 (code
, resp
) = self
.docmd("AUTH",
552 AUTH_PLAIN
+ " " + encode_plain(user
, password
))
553 elif authmethod
is None:
554 raise SMTPException("No suitable authentication method found.")
555 if code
not in [235, 503]:
556 # 235 == 'Authentication successful'
557 # 503 == 'Error: already authenticated'
558 raise SMTPAuthenticationError(code
, resp
)
561 def starttls(self
, keyfile
= None, certfile
= None):
562 """Puts the connection to the SMTP server into TLS mode.
564 If the server supports TLS, this will encrypt the rest of the SMTP
565 session. If you provide the keyfile and certfile parameters,
566 the identity of the SMTP server and client can be checked. This,
567 however, depends on whether the socket module really checks the
570 (resp
, reply
) = self
.docmd("STARTTLS")
572 sslobj
= socket
.ssl(self
.sock
, keyfile
, certfile
)
573 self
.sock
= SSLFakeSocket(self
.sock
, sslobj
)
574 self
.file = SSLFakeFile(sslobj
)
577 def sendmail(self
, from_addr
, to_addrs
, msg
, mail_options
=[],
579 """This command performs an entire mail transaction.
582 - from_addr : The address sending this mail.
583 - to_addrs : A list of addresses to send this mail to. A bare
584 string will be treated as a list with 1 address.
585 - msg : The message to send.
586 - mail_options : List of ESMTP options (such as 8bitmime) for the
588 - rcpt_options : List of ESMTP options (such as DSN commands) for
589 all the rcpt commands.
591 If there has been no previous EHLO or HELO command this session, this
592 method tries ESMTP EHLO first. If the server does ESMTP, message size
593 and each of the specified options will be passed to it. If EHLO
594 fails, HELO will be tried and ESMTP options suppressed.
596 This method will return normally if the mail is accepted for at least
597 one recipient. It returns a dictionary, with one entry for each
598 recipient that was refused. Each entry contains a tuple of the SMTP
599 error code and the accompanying error message sent by the server.
601 This method may raise the following exceptions:
603 SMTPHeloError The server didn't reply properly to
605 SMTPRecipientsRefused The server rejected ALL recipients
607 SMTPSenderRefused The server didn't accept the from_addr.
608 SMTPDataError The server replied with an unexpected
609 error code (other than a refusal of
612 Note: the connection will be open even after an exception is raised.
617 >>> s=smtplib.SMTP("localhost")
618 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
621 ... Subject: testin'...
623 ... This is a test '''
624 >>> s.sendmail("me@my.org",tolist,msg)
625 { "three@three.org" : ( 550 ,"User unknown" ) }
628 In the above example, the message was accepted for delivery to three
629 of the four addresses, and one was rejected, with the error code
630 550. If all addresses are accepted, then the method will return an
634 if self
.helo_resp
is None and self
.ehlo_resp
is None:
635 if not (200 <= self
.ehlo()[0] <= 299):
636 (code
,resp
) = self
.helo()
637 if not (200 <= code
<= 299):
638 raise SMTPHeloError(code
, resp
)
641 # Hmmm? what's this? -ddm
642 # self.esmtp_features['7bit']=""
643 if self
.has_extn('size'):
644 esmtp_opts
.append("size=" + `
len(msg
)`
)
645 for option
in mail_options
:
646 esmtp_opts
.append(option
)
648 (code
,resp
) = self
.mail(from_addr
, esmtp_opts
)
651 raise SMTPSenderRefused(code
, resp
, from_addr
)
653 if isinstance(to_addrs
, basestring
):
654 to_addrs
= [to_addrs
]
655 for each
in to_addrs
:
656 (code
,resp
)=self
.rcpt(each
, rcpt_options
)
657 if (code
!= 250) and (code
!= 251):
658 senderrs
[each
]=(code
,resp
)
659 if len(senderrs
)==len(to_addrs
):
660 # the server refused all our recipients
662 raise SMTPRecipientsRefused(senderrs
)
663 (code
,resp
) = self
.data(msg
)
666 raise SMTPDataError(code
, resp
)
667 #if we got here then somebody got our mail
672 """Close the connection to the SMTP server."""
682 """Terminate the SMTP session."""
687 # Test the sendmail method, which tests most of the others.
688 # Note: This always sends to localhost.
689 if __name__
== '__main__':
693 sys
.stdout
.write(prompt
+ ": ")
694 return sys
.stdin
.readline().strip()
696 fromaddr
= prompt("From")
697 toaddrs
= prompt("To").split(',')
698 print "Enter message, end with ^D:"
701 line
= sys
.stdin
.readline()
705 print "Message length is " + `
len(msg
)`
707 server
= SMTP('localhost')
708 server
.set_debuglevel(1)
709 server
.sendmail(fromaddr
, toaddrs
, msg
)