|
1 #! /usr/bin/env python |
|
2 |
|
3 '''SMTP/ESMTP client class. |
|
4 |
|
5 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP |
|
6 Authentication) and RFC 2487 (Secure SMTP over TLS). |
|
7 |
|
8 Notes: |
|
9 |
|
10 Please remember, when doing ESMTP, that the names of the SMTP service |
|
11 extensions are NOT the same thing as the option keywords for the RCPT |
|
12 and MAIL commands! |
|
13 |
|
14 Example: |
|
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 |
|
44 import socket |
|
45 import re |
|
46 import email.utils |
|
47 import base64 |
|
48 import hmac |
|
49 from email.base64mime import encode as encode_base64 |
|
50 from sys import stderr |
|
51 |
|
52 __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException", |
|
53 "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError", |
|
54 "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError", |
|
55 "quoteaddr","quotedata","SMTP"] |
|
56 |
|
57 SMTP_PORT = 25 |
|
58 SMTP_SSL_PORT = 465 |
|
59 CRLF="\r\n" |
|
60 |
|
61 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I) |
|
62 |
|
63 # Exception classes used by this module. |
|
64 class SMTPException(Exception): |
|
65 """Base class for all exceptions raised by this module.""" |
|
66 |
|
67 class 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 |
|
75 class 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 |
|
89 class 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 |
|
102 class 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 |
|
115 class SMTPDataError(SMTPResponseException): |
|
116 """The SMTP server didn't accept the data.""" |
|
117 |
|
118 class SMTPConnectError(SMTPResponseException): |
|
119 """Error during connection establishment.""" |
|
120 |
|
121 class SMTPHeloError(SMTPResponseException): |
|
122 """The server refused our HELO reply.""" |
|
123 |
|
124 class SMTPAuthenticationError(SMTPResponseException): |
|
125 """Authentication error. |
|
126 |
|
127 Most probably the server didn't accept the username/password |
|
128 combination provided. |
|
129 """ |
|
130 |
|
131 def 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 |
|
150 def 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 |
|
160 try: |
|
161 import ssl |
|
162 except ImportError: |
|
163 _have_ssl = False |
|
164 else: |
|
165 class SSLFakeFile: |
|
166 """A fake file like object that really wraps a SSLObject. |
|
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 |
|
187 class 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 |
|
734 if _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 self.sock = socket.create_connection((host, port), timeout) |
|
755 self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile) |
|
756 self.file = SSLFakeFile(self.sock) |
|
757 |
|
758 __all__.append("SMTP_SSL") |
|
759 |
|
760 # |
|
761 # LMTP extension |
|
762 # |
|
763 LMTP_PORT = 2003 |
|
764 |
|
765 class LMTP(SMTP): |
|
766 """LMTP - Local Mail Transfer Protocol |
|
767 |
|
768 The LMTP protocol, which is very similar to ESMTP, is heavily based |
|
769 on the standard SMTP client. It's common to use Unix sockets for LMTP, |
|
770 so our connect() method must support that as well as a regular |
|
771 host:port server. To specify a Unix socket, you must use an absolute |
|
772 path as the host, starting with a '/'. |
|
773 |
|
774 Authentication is supported, using the regular SMTP mechanism. When |
|
775 using a Unix socket, LMTP generally don't support or require any |
|
776 authentication, but your mileage might vary.""" |
|
777 |
|
778 ehlo_msg = "lhlo" |
|
779 |
|
780 def __init__(self, host = '', port = LMTP_PORT, local_hostname = None): |
|
781 """Initialize a new instance.""" |
|
782 SMTP.__init__(self, host, port, local_hostname) |
|
783 |
|
784 def connect(self, host = 'localhost', port = 0): |
|
785 """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" |
|
786 if host[0] != '/': |
|
787 return SMTP.connect(self, host, port) |
|
788 |
|
789 # Handle Unix-domain sockets. |
|
790 try: |
|
791 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
|
792 self.sock.connect(host) |
|
793 except socket.error, msg: |
|
794 if self.debuglevel > 0: print>>stderr, 'connect fail:', host |
|
795 if self.sock: |
|
796 self.sock.close() |
|
797 self.sock = None |
|
798 raise socket.error, msg |
|
799 (code, msg) = self.getreply() |
|
800 if self.debuglevel > 0: print>>stderr, "connect:", msg |
|
801 return (code, msg) |
|
802 |
|
803 |
|
804 # Test the sendmail method, which tests most of the others. |
|
805 # Note: This always sends to localhost. |
|
806 if __name__ == '__main__': |
|
807 import sys |
|
808 |
|
809 def prompt(prompt): |
|
810 sys.stdout.write(prompt + ": ") |
|
811 return sys.stdin.readline().strip() |
|
812 |
|
813 fromaddr = prompt("From") |
|
814 toaddrs = prompt("To").split(',') |
|
815 print "Enter message, end with ^D:" |
|
816 msg = '' |
|
817 while 1: |
|
818 line = sys.stdin.readline() |
|
819 if not line: |
|
820 break |
|
821 msg = msg + line |
|
822 print "Message length is %d" % len(msg) |
|
823 |
|
824 server = SMTP('localhost') |
|
825 server.set_debuglevel(1) |
|
826 server.sendmail(fromaddr, toaddrs, msg) |
|
827 server.quit() |