source: python/trunk/Lib/smtplib.py@ 610

Last change on this file since 610 was 391, checked in by dmik, 11 years ago

python: Merge vendor 2.7.6 to trunk.

  • Property svn:eol-style set to native
File size: 30.6 KB
Line 
1#! /usr/bin/env python
2
3'''SMTP/ESMTP client class.
4
5This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
6Authentication) and RFC 2487 (Secure SMTP over TLS).
7
8Notes:
9
10Please remember, when doing ESMTP, that the names of the SMTP service
11extensions are NOT the same thing as the option keywords for the RCPT
12and MAIL commands!
13
14Example:
15
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'''
34
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>.
41#
42# This was modified from the Python 1.5 library HTTP lib.
43
44import socket
45import re
46import email.utils
47import base64
48import hmac
49from email.base64mime import encode as encode_base64
50from sys import stderr
51
52__all__ = ["SMTPException", "SMTPServerDisconnected", "SMTPResponseException",
53 "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError",
54 "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError",
55 "quoteaddr", "quotedata", "SMTP"]
56
57SMTP_PORT = 25
58SMTP_SSL_PORT = 465
59CRLF = "\r\n"
60
61OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
62
63
64# Exception classes used by this module.
65class SMTPException(Exception):
66 """Base class for all exceptions raised by this module."""
67
68class SMTPServerDisconnected(SMTPException):
69 """Not connected to any SMTP server.
70
71 This exception is raised when the server unexpectedly disconnects,
72 or when an attempt is made to use the SMTP instance before
73 connecting it to a server.
74 """
75
76class SMTPResponseException(SMTPException):
77 """Base class for all exceptions that include an SMTP error code.
78
79 These exceptions are generated in some instances when the SMTP
80 server returns an error code. The error code is stored in the
81 `smtp_code' attribute of the error, and the `smtp_error' attribute
82 is set to the error message.
83 """
84
85 def __init__(self, code, msg):
86 self.smtp_code = code
87 self.smtp_error = msg
88 self.args = (code, msg)
89
90class SMTPSenderRefused(SMTPResponseException):
91 """Sender address refused.
92
93 In addition to the attributes set by on all SMTPResponseException
94 exceptions, this sets `sender' to the string that the SMTP refused.
95 """
96
97 def __init__(self, code, msg, sender):
98 self.smtp_code = code
99 self.smtp_error = msg
100 self.sender = sender
101 self.args = (code, msg, sender)
102
103class SMTPRecipientsRefused(SMTPException):
104 """All recipient addresses refused.
105
106 The errors for each recipient are accessible through the attribute
107 'recipients', which is a dictionary of exactly the same sort as
108 SMTP.sendmail() returns.
109 """
110
111 def __init__(self, recipients):
112 self.recipients = recipients
113 self.args = (recipients,)
114
115
116class SMTPDataError(SMTPResponseException):
117 """The SMTP server didn't accept the data."""
118
119class SMTPConnectError(SMTPResponseException):
120 """Error during connection establishment."""
121
122class SMTPHeloError(SMTPResponseException):
123 """The server refused our HELO reply."""
124
125class SMTPAuthenticationError(SMTPResponseException):
126 """Authentication error.
127
128 Most probably the server didn't accept the username/password
129 combination provided.
130 """
131
132
133def quoteaddr(addr):
134 """Quote a subset of the email addresses defined by RFC 821.
135
136 Should be able to handle anything rfc822.parseaddr can handle.
137 """
138 m = (None, None)
139 try:
140 m = email.utils.parseaddr(addr)[1]
141 except AttributeError:
142 pass
143 if m == (None, None): # Indicates parse failure or AttributeError
144 # something weird here.. punt -ddm
145 return "<%s>" % addr
146 elif m is None:
147 # the sender wants an empty return address
148 return "<>"
149 else:
150 return "<%s>" % m
151
152def _addr_only(addrstring):
153 displayname, addr = email.utils.parseaddr(addrstring)
154 if (displayname, addr) == ('', ''):
155 # parseaddr couldn't parse it, so use it as is.
156 return addrstring
157 return addr
158
159def quotedata(data):
160 """Quote data for email.
161
162 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
163 Internet CRLF end-of-line.
164 """
165 return re.sub(r'(?m)^\.', '..',
166 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
167
168
169try:
170 import ssl
171except ImportError:
172 _have_ssl = False
173else:
174 class SSLFakeFile:
175 """A fake file like object that really wraps a SSLObject.
176
177 It only supports what is needed in smtplib.
178 """
179 def __init__(self, sslobj):
180 self.sslobj = sslobj
181
182 def readline(self):
183 str = ""
184 chr = None
185 while chr != "\n":
186 chr = self.sslobj.read(1)
187 if not chr:
188 break
189 str += chr
190 return str
191
192 def close(self):
193 pass
194
195 _have_ssl = True
196
197class 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.
204
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.
208
209 does_esmtp
210 This is a True value _after you do an EHLO command_, if the
211 server supports ESMTP.
212
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).
218
219 Note, all extension names are mapped to lower case in the
220 dictionary.
221
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.
225 """
226 debuglevel = 0
227 file = None
228 helo_resp = None
229 ehlo_msg = "ehlo"
230 ehlo_resp = None
231 does_esmtp = 0
232 default_port = SMTP_PORT
233
234 def __init__(self, host='', port=0, local_hostname=None,
235 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
236 """Initialize a new instance.
237
238 If specified, `host' is the name of the remote host to which to
239 connect. If specified, `port' specifies the port to which to connect.
240 By default, smtplib.SMTP_PORT is used. If a host is specified the
241 connect method is called, and if it returns anything other than a
242 success code an SMTPConnectError is raised. If specified,
243 `local_hostname` is used as the FQDN of the local host for the
244 HELO/EHLO command. Otherwise, the local hostname is found using
245 socket.getfqdn().
246
247 """
248 self.timeout = timeout
249 self.esmtp_features = {}
250 if host:
251 (code, msg) = self.connect(host, port)
252 if code != 220:
253 raise SMTPConnectError(code, msg)
254 if local_hostname is not None:
255 self.local_hostname = local_hostname
256 else:
257 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
258 # if that can't be calculated, that we should use a domain literal
259 # instead (essentially an encoded IP address like [A.B.C.D]).
260 fqdn = socket.getfqdn()
261 if '.' in fqdn:
262 self.local_hostname = fqdn
263 else:
264 # We can't find an fqdn hostname, so use a domain literal
265 addr = '127.0.0.1'
266 try:
267 addr = socket.gethostbyname(socket.gethostname())
268 except socket.gaierror:
269 pass
270 self.local_hostname = '[%s]' % addr
271
272 def set_debuglevel(self, debuglevel):
273 """Set the debug output level.
274
275 A non-false value results in debug messages for connection and for all
276 messages sent to and received from the server.
277
278 """
279 self.debuglevel = debuglevel
280
281 def _get_socket(self, host, port, timeout):
282 # This makes it simpler for SMTP_SSL to use the SMTP connect code
283 # and just alter the socket connection bit.
284 if self.debuglevel > 0:
285 print>>stderr, 'connect:', (host, port)
286 return socket.create_connection((host, port), timeout)
287
288 def connect(self, host='localhost', port=0):
289 """Connect to a host on a given port.
290
291 If the hostname ends with a colon (`:') followed by a number, and
292 there is no port specified, that suffix will be stripped off and the
293 number interpreted as the port number to use.
294
295 Note: This method is automatically invoked by __init__, if a host is
296 specified during instantiation.
297
298 """
299 if not port and (host.find(':') == host.rfind(':')):
300 i = host.rfind(':')
301 if i >= 0:
302 host, port = host[:i], host[i + 1:]
303 try:
304 port = int(port)
305 except ValueError:
306 raise socket.error, "nonnumeric port"
307 if not port:
308 port = self.default_port
309 if self.debuglevel > 0:
310 print>>stderr, 'connect:', (host, port)
311 self.sock = self._get_socket(host, port, self.timeout)
312 (code, msg) = self.getreply()
313 if self.debuglevel > 0:
314 print>>stderr, "connect:", msg
315 return (code, msg)
316
317 def send(self, str):
318 """Send `str' to the server."""
319 if self.debuglevel > 0:
320 print>>stderr, 'send:', repr(str)
321 if hasattr(self, 'sock') and self.sock:
322 try:
323 self.sock.sendall(str)
324 except socket.error:
325 self.close()
326 raise SMTPServerDisconnected('Server not connected')
327 else:
328 raise SMTPServerDisconnected('please run connect() first')
329
330 def putcmd(self, cmd, args=""):
331 """Send a command to the server."""
332 if args == "":
333 str = '%s%s' % (cmd, CRLF)
334 else:
335 str = '%s %s%s' % (cmd, args, CRLF)
336 self.send(str)
337
338 def getreply(self):
339 """Get a reply from the server.
340
341 Returns a tuple consisting of:
342
343 - server response code (e.g. '250', or such, if all goes well)
344 Note: returns -1 if it can't read response code.
345
346 - server response string corresponding to response code (multiline
347 responses are converted to a single, multiline string).
348
349 Raises SMTPServerDisconnected if end-of-file is reached.
350 """
351 resp = []
352 if self.file is None:
353 self.file = self.sock.makefile('rb')
354 while 1:
355 try:
356 line = self.file.readline()
357 except socket.error as e:
358 self.close()
359 raise SMTPServerDisconnected("Connection unexpectedly closed: "
360 + str(e))
361 if line == '':
362 self.close()
363 raise SMTPServerDisconnected("Connection unexpectedly closed")
364 if self.debuglevel > 0:
365 print>>stderr, 'reply:', repr(line)
366 resp.append(line[4:].strip())
367 code = line[:3]
368 # Check that the error code is syntactically correct.
369 # Don't attempt to read a continuation line if it is broken.
370 try:
371 errcode = int(code)
372 except ValueError:
373 errcode = -1
374 break
375 # Check if multiline response.
376 if line[3:4] != "-":
377 break
378
379 errmsg = "\n".join(resp)
380 if self.debuglevel > 0:
381 print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode, errmsg)
382 return errcode, errmsg
383
384 def docmd(self, cmd, args=""):
385 """Send a command, and return its response code."""
386 self.putcmd(cmd, args)
387 return self.getreply()
388
389 # std smtp commands
390 def helo(self, name=''):
391 """SMTP 'helo' command.
392 Hostname to send for this command defaults to the FQDN of the local
393 host.
394 """
395 self.putcmd("helo", name or self.local_hostname)
396 (code, msg) = self.getreply()
397 self.helo_resp = msg
398 return (code, msg)
399
400 def ehlo(self, name=''):
401 """ SMTP 'ehlo' command.
402 Hostname to send for this command defaults to the FQDN of the local
403 host.
404 """
405 self.esmtp_features = {}
406 self.putcmd(self.ehlo_msg, name or self.local_hostname)
407 (code, msg) = self.getreply()
408 # According to RFC1869 some (badly written)
409 # MTA's will disconnect on an ehlo. Toss an exception if
410 # that happens -ddm
411 if code == -1 and len(msg) == 0:
412 self.close()
413 raise SMTPServerDisconnected("Server not connected")
414 self.ehlo_resp = msg
415 if code != 250:
416 return (code, msg)
417 self.does_esmtp = 1
418 #parse the ehlo response -ddm
419 resp = self.ehlo_resp.split('\n')
420 del resp[0]
421 for each in resp:
422 # To be able to communicate with as many SMTP servers as possible,
423 # we have to take the old-style auth advertisement into account,
424 # because:
425 # 1) Else our SMTP feature parser gets confused.
426 # 2) There are some servers that only advertise the auth methods we
427 # support using the old style.
428 auth_match = OLDSTYLE_AUTH.match(each)
429 if auth_match:
430 # This doesn't remove duplicates, but that's no problem
431 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
432 + " " + auth_match.groups(0)[0]
433 continue
434
435 # RFC 1869 requires a space between ehlo keyword and parameters.
436 # It's actually stricter, in that only spaces are allowed between
437 # parameters, but were not going to check for that here. Note
438 # that the space isn't present if there are no parameters.
439 m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each)
440 if m:
441 feature = m.group("feature").lower()
442 params = m.string[m.end("feature"):].strip()
443 if feature == "auth":
444 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
445 + " " + params
446 else:
447 self.esmtp_features[feature] = params
448 return (code, msg)
449
450 def has_extn(self, opt):
451 """Does the server support a given SMTP service extension?"""
452 return opt.lower() in self.esmtp_features
453
454 def help(self, args=''):
455 """SMTP 'help' command.
456 Returns help text from server."""
457 self.putcmd("help", args)
458 return self.getreply()[1]
459
460 def rset(self):
461 """SMTP 'rset' command -- resets session."""
462 return self.docmd("rset")
463
464 def noop(self):
465 """SMTP 'noop' command -- doesn't do anything :>"""
466 return self.docmd("noop")
467
468 def mail(self, sender, options=[]):
469 """SMTP 'mail' command -- begins mail xfer session."""
470 optionlist = ''
471 if options and self.does_esmtp:
472 optionlist = ' ' + ' '.join(options)
473 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
474 return self.getreply()
475
476 def rcpt(self, recip, options=[]):
477 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
478 optionlist = ''
479 if options and self.does_esmtp:
480 optionlist = ' ' + ' '.join(options)
481 self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist))
482 return self.getreply()
483
484 def data(self, msg):
485 """SMTP 'DATA' command -- sends message data to server.
486
487 Automatically quotes lines beginning with a period per rfc821.
488 Raises SMTPDataError if there is an unexpected reply to the
489 DATA command; the return value from this method is the final
490 response code received when the all data is sent.
491 """
492 self.putcmd("data")
493 (code, repl) = self.getreply()
494 if self.debuglevel > 0:
495 print>>stderr, "data:", (code, repl)
496 if code != 354:
497 raise SMTPDataError(code, repl)
498 else:
499 q = quotedata(msg)
500 if q[-2:] != CRLF:
501 q = q + CRLF
502 q = q + "." + CRLF
503 self.send(q)
504 (code, msg) = self.getreply()
505 if self.debuglevel > 0:
506 print>>stderr, "data:", (code, msg)
507 return (code, msg)
508
509 def verify(self, address):
510 """SMTP 'verify' command -- checks for address validity."""
511 self.putcmd("vrfy", _addr_only(address))
512 return self.getreply()
513 # a.k.a.
514 vrfy = verify
515
516 def expn(self, address):
517 """SMTP 'expn' command -- expands a mailing list."""
518 self.putcmd("expn", _addr_only(address))
519 return self.getreply()
520
521 # some useful methods
522
523 def ehlo_or_helo_if_needed(self):
524 """Call self.ehlo() and/or self.helo() if needed.
525
526 If there has been no previous EHLO or HELO command this session, this
527 method tries ESMTP EHLO first.
528
529 This method may raise the following exceptions:
530
531 SMTPHeloError The server didn't reply properly to
532 the helo greeting.
533 """
534 if self.helo_resp is None and self.ehlo_resp is None:
535 if not (200 <= self.ehlo()[0] <= 299):
536 (code, resp) = self.helo()
537 if not (200 <= code <= 299):
538 raise SMTPHeloError(code, resp)
539
540 def login(self, user, password):
541 """Log in on an SMTP server that requires authentication.
542
543 The arguments are:
544 - user: The user name to authenticate with.
545 - password: The password for the authentication.
546
547 If there has been no previous EHLO or HELO command this session, this
548 method tries ESMTP EHLO first.
549
550 This method will return normally if the authentication was successful.
551
552 This method may raise the following exceptions:
553
554 SMTPHeloError The server didn't reply properly to
555 the helo greeting.
556 SMTPAuthenticationError The server didn't accept the username/
557 password combination.
558 SMTPException No suitable authentication method was
559 found.
560 """
561
562 def encode_cram_md5(challenge, user, password):
563 challenge = base64.decodestring(challenge)
564 response = user + " " + hmac.HMAC(password, challenge).hexdigest()
565 return encode_base64(response, eol="")
566
567 def encode_plain(user, password):
568 return encode_base64("\0%s\0%s" % (user, password), eol="")
569
570
571 AUTH_PLAIN = "PLAIN"
572 AUTH_CRAM_MD5 = "CRAM-MD5"
573 AUTH_LOGIN = "LOGIN"
574
575 self.ehlo_or_helo_if_needed()
576
577 if not self.has_extn("auth"):
578 raise SMTPException("SMTP AUTH extension not supported by server.")
579
580 # Authentication methods the server supports:
581 authlist = self.esmtp_features["auth"].split()
582
583 # List of authentication methods we support: from preferred to
584 # less preferred methods. Except for the purpose of testing the weaker
585 # ones, we prefer stronger methods like CRAM-MD5:
586 preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
587
588 # Determine the authentication method we'll use
589 authmethod = None
590 for method in preferred_auths:
591 if method in authlist:
592 authmethod = method
593 break
594
595 if authmethod == AUTH_CRAM_MD5:
596 (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
597 if code == 503:
598 # 503 == 'Error: already authenticated'
599 return (code, resp)
600 (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
601 elif authmethod == AUTH_PLAIN:
602 (code, resp) = self.docmd("AUTH",
603 AUTH_PLAIN + " " + encode_plain(user, password))
604 elif authmethod == AUTH_LOGIN:
605 (code, resp) = self.docmd("AUTH",
606 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
607 if code != 334:
608 raise SMTPAuthenticationError(code, resp)
609 (code, resp) = self.docmd(encode_base64(password, eol=""))
610 elif authmethod is None:
611 raise SMTPException("No suitable authentication method found.")
612 if code not in (235, 503):
613 # 235 == 'Authentication successful'
614 # 503 == 'Error: already authenticated'
615 raise SMTPAuthenticationError(code, resp)
616 return (code, resp)
617
618 def starttls(self, keyfile=None, certfile=None):
619 """Puts the connection to the SMTP server into TLS mode.
620
621 If there has been no previous EHLO or HELO command this session, this
622 method tries ESMTP EHLO first.
623
624 If the server supports TLS, this will encrypt the rest of the SMTP
625 session. If you provide the keyfile and certfile parameters,
626 the identity of the SMTP server and client can be checked. This,
627 however, depends on whether the socket module really checks the
628 certificates.
629
630 This method may raise the following exceptions:
631
632 SMTPHeloError The server didn't reply properly to
633 the helo greeting.
634 """
635 self.ehlo_or_helo_if_needed()
636 if not self.has_extn("starttls"):
637 raise SMTPException("STARTTLS extension not supported by server.")
638 (resp, reply) = self.docmd("STARTTLS")
639 if resp == 220:
640 if not _have_ssl:
641 raise RuntimeError("No SSL support included in this Python")
642 self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
643 self.file = SSLFakeFile(self.sock)
644 # RFC 3207:
645 # The client MUST discard any knowledge obtained from
646 # the server, such as the list of SMTP service extensions,
647 # which was not obtained from the TLS negotiation itself.
648 self.helo_resp = None
649 self.ehlo_resp = None
650 self.esmtp_features = {}
651 self.does_esmtp = 0
652 return (resp, reply)
653
654 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
655 rcpt_options=[]):
656 """This command performs an entire mail transaction.
657
658 The arguments are:
659 - from_addr : The address sending this mail.
660 - to_addrs : A list of addresses to send this mail to. A bare
661 string will be treated as a list with 1 address.
662 - msg : The message to send.
663 - mail_options : List of ESMTP options (such as 8bitmime) for the
664 mail command.
665 - rcpt_options : List of ESMTP options (such as DSN commands) for
666 all the rcpt commands.
667
668 If there has been no previous EHLO or HELO command this session, this
669 method tries ESMTP EHLO first. If the server does ESMTP, message size
670 and each of the specified options will be passed to it. If EHLO
671 fails, HELO will be tried and ESMTP options suppressed.
672
673 This method will return normally if the mail is accepted for at least
674 one recipient. It returns a dictionary, with one entry for each
675 recipient that was refused. Each entry contains a tuple of the SMTP
676 error code and the accompanying error message sent by the server.
677
678 This method may raise the following exceptions:
679
680 SMTPHeloError The server didn't reply properly to
681 the helo greeting.
682 SMTPRecipientsRefused The server rejected ALL recipients
683 (no mail was sent).
684 SMTPSenderRefused The server didn't accept the from_addr.
685 SMTPDataError The server replied with an unexpected
686 error code (other than a refusal of
687 a recipient).
688
689 Note: the connection will be open even after an exception is raised.
690
691 Example:
692
693 >>> import smtplib
694 >>> s=smtplib.SMTP("localhost")
695 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
696 >>> msg = '''\\
697 ... From: Me@my.org
698 ... Subject: testin'...
699 ...
700 ... This is a test '''
701 >>> s.sendmail("me@my.org",tolist,msg)
702 { "three@three.org" : ( 550 ,"User unknown" ) }
703 >>> s.quit()
704
705 In the above example, the message was accepted for delivery to three
706 of the four addresses, and one was rejected, with the error code
707 550. If all addresses are accepted, then the method will return an
708 empty dictionary.
709
710 """
711 self.ehlo_or_helo_if_needed()
712 esmtp_opts = []
713 if self.does_esmtp:
714 # Hmmm? what's this? -ddm
715 # self.esmtp_features['7bit']=""
716 if self.has_extn('size'):
717 esmtp_opts.append("size=%d" % len(msg))
718 for option in mail_options:
719 esmtp_opts.append(option)
720
721 (code, resp) = self.mail(from_addr, esmtp_opts)
722 if code != 250:
723 self.rset()
724 raise SMTPSenderRefused(code, resp, from_addr)
725 senderrs = {}
726 if isinstance(to_addrs, basestring):
727 to_addrs = [to_addrs]
728 for each in to_addrs:
729 (code, resp) = self.rcpt(each, rcpt_options)
730 if (code != 250) and (code != 251):
731 senderrs[each] = (code, resp)
732 if len(senderrs) == len(to_addrs):
733 # the server refused all our recipients
734 self.rset()
735 raise SMTPRecipientsRefused(senderrs)
736 (code, resp) = self.data(msg)
737 if code != 250:
738 self.rset()
739 raise SMTPDataError(code, resp)
740 #if we got here then somebody got our mail
741 return senderrs
742
743
744 def close(self):
745 """Close the connection to the SMTP server."""
746 if self.file:
747 self.file.close()
748 self.file = None
749 if self.sock:
750 self.sock.close()
751 self.sock = None
752
753
754 def quit(self):
755 """Terminate the SMTP session."""
756 res = self.docmd("quit")
757 self.close()
758 return res
759
760if _have_ssl:
761
762 class SMTP_SSL(SMTP):
763 """ This is a subclass derived from SMTP that connects over an SSL
764 encrypted socket (to use this class you need a socket module that was
765 compiled with SSL support). If host is not specified, '' (the local
766 host) is used. If port is omitted, the standard SMTP-over-SSL port
767 (465) is used. local_hostname has the same meaning as it does in the
768 SMTP class. keyfile and certfile are also optional - they can contain
769 a PEM formatted private key and certificate chain file for the SSL
770 connection.
771
772 """
773
774 default_port = SMTP_SSL_PORT
775
776 def __init__(self, host='', port=0, local_hostname=None,
777 keyfile=None, certfile=None,
778 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
779 self.keyfile = keyfile
780 self.certfile = certfile
781 SMTP.__init__(self, host, port, local_hostname, timeout)
782
783 def _get_socket(self, host, port, timeout):
784 if self.debuglevel > 0:
785 print>>stderr, 'connect:', (host, port)
786 new_socket = socket.create_connection((host, port), timeout)
787 new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
788 self.file = SSLFakeFile(new_socket)
789 return new_socket
790
791 __all__.append("SMTP_SSL")
792
793#
794# LMTP extension
795#
796LMTP_PORT = 2003
797
798class LMTP(SMTP):
799 """LMTP - Local Mail Transfer Protocol
800
801 The LMTP protocol, which is very similar to ESMTP, is heavily based
802 on the standard SMTP client. It's common to use Unix sockets for
803 LMTP, so our connect() method must support that as well as a regular
804 host:port server. local_hostname has the same meaning as it does in
805 the SMTP class. To specify a Unix socket, you must use an absolute
806 path as the host, starting with a '/'.
807
808 Authentication is supported, using the regular SMTP mechanism. When
809 using a Unix socket, LMTP generally don't support or require any
810 authentication, but your mileage might vary."""
811
812 ehlo_msg = "lhlo"
813
814 def __init__(self, host='', port=LMTP_PORT, local_hostname=None):
815 """Initialize a new instance."""
816 SMTP.__init__(self, host, port, local_hostname)
817
818 def connect(self, host='localhost', port=0):
819 """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
820 if host[0] != '/':
821 return SMTP.connect(self, host, port)
822
823 # Handle Unix-domain sockets.
824 try:
825 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
826 self.sock.connect(host)
827 except socket.error:
828 if self.debuglevel > 0:
829 print>>stderr, 'connect fail:', host
830 if self.sock:
831 self.sock.close()
832 self.sock = None
833 raise
834 (code, msg) = self.getreply()
835 if self.debuglevel > 0:
836 print>>stderr, "connect:", msg
837 return (code, msg)
838
839
840# Test the sendmail method, which tests most of the others.
841# Note: This always sends to localhost.
842if __name__ == '__main__':
843 import sys
844
845 def prompt(prompt):
846 sys.stdout.write(prompt + ": ")
847 return sys.stdin.readline().strip()
848
849 fromaddr = prompt("From")
850 toaddrs = prompt("To").split(',')
851 print "Enter message, end with ^D:"
852 msg = ''
853 while 1:
854 line = sys.stdin.readline()
855 if not line:
856 break
857 msg = msg + line
858 print "Message length is %d" % len(msg)
859
860 server = SMTP('localhost')
861 server.set_debuglevel(1)
862 server.sendmail(fromaddr, toaddrs, msg)
863 server.quit()
Note: See TracBrowser for help on using the repository browser.