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