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

Last change on this file since 390 was 2, checked in by Yuri Dario, 15 years ago

Initial import for vendor code.

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