PyXR

c:\python24\lib \ smtplib.py



0001 #! /usr/bin/env python
0002 
0003 '''SMTP/ESMTP client class.
0004 
0005 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
0006 Authentication) and RFC 2487 (Secure SMTP over TLS).
0007 
0008 Notes:
0009 
0010 Please remember, when doing ESMTP, that the names of the SMTP service
0011 extensions are NOT the same thing as the option keywords for the RCPT
0012 and MAIL commands!
0013 
0014 Example:
0015 
0016   >>> import smtplib
0017   >>> s=smtplib.SMTP("localhost")
0018   >>> print s.help()
0019   This is Sendmail version 8.8.4
0020   Topics:
0021       HELO    EHLO    MAIL    RCPT    DATA
0022       RSET    NOOP    QUIT    HELP    VRFY
0023       EXPN    VERB    ETRN    DSN
0024   For more info use "HELP <topic>".
0025   To report bugs in the implementation send email to
0026       sendmail-bugs@sendmail.org.
0027   For local information send email to Postmaster at your site.
0028   End of HELP info
0029   >>> s.putcmd("vrfy","someone@here")
0030   >>> s.getreply()
0031   (250, "Somebody OverHere <somebody@here.my.org>")
0032   >>> s.quit()
0033 '''
0034 
0035 # Author: The Dragon De Monsyne <dragondm@integral.org>
0036 # ESMTP support, test code and doc fixes added by
0037 #     Eric S. Raymond <esr@thyrsus.com>
0038 # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
0039 #     by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
0040 # RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>.
0041 #
0042 # This was modified from the Python 1.5 library HTTP lib.
0043 
0044 import socket
0045 import re
0046 import rfc822
0047 import base64
0048 import hmac
0049 from email.base64MIME import encode as encode_base64
0050 from sys import stderr
0051 
0052 __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
0053            "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError",
0054            "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError",
0055            "quoteaddr","quotedata","SMTP"]
0056 
0057 SMTP_PORT = 25
0058 CRLF="\r\n"
0059 
0060 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
0061 
0062 # Exception classes used by this module.
0063 class SMTPException(Exception):
0064     """Base class for all exceptions raised by this module."""
0065 
0066 class SMTPServerDisconnected(SMTPException):
0067     """Not connected to any SMTP server.
0068 
0069     This exception is raised when the server unexpectedly disconnects,
0070     or when an attempt is made to use the SMTP instance before
0071     connecting it to a server.
0072     """
0073 
0074 class SMTPResponseException(SMTPException):
0075     """Base class for all exceptions that include an SMTP error code.
0076 
0077     These exceptions are generated in some instances when the SMTP
0078     server returns an error code.  The error code is stored in the
0079     `smtp_code' attribute of the error, and the `smtp_error' attribute
0080     is set to the error message.
0081     """
0082 
0083     def __init__(self, code, msg):
0084         self.smtp_code = code
0085         self.smtp_error = msg
0086         self.args = (code, msg)
0087 
0088 class SMTPSenderRefused(SMTPResponseException):
0089     """Sender address refused.
0090 
0091     In addition to the attributes set by on all SMTPResponseException
0092     exceptions, this sets `sender' to the string that the SMTP refused.
0093     """
0094 
0095     def __init__(self, code, msg, sender):
0096         self.smtp_code = code
0097         self.smtp_error = msg
0098         self.sender = sender
0099         self.args = (code, msg, sender)
0100 
0101 class SMTPRecipientsRefused(SMTPException):
0102     """All recipient addresses refused.
0103 
0104     The errors for each recipient are accessible through the attribute
0105     'recipients', which is a dictionary of exactly the same sort as
0106     SMTP.sendmail() returns.
0107     """
0108 
0109     def __init__(self, recipients):
0110         self.recipients = recipients
0111         self.args = ( recipients,)
0112 
0113 
0114 class SMTPDataError(SMTPResponseException):
0115     """The SMTP server didn't accept the data."""
0116 
0117 class SMTPConnectError(SMTPResponseException):
0118     """Error during connection establishment."""
0119 
0120 class SMTPHeloError(SMTPResponseException):
0121     """The server refused our HELO reply."""
0122 
0123 class SMTPAuthenticationError(SMTPResponseException):
0124     """Authentication error.
0125 
0126     Most probably the server didn't accept the username/password
0127     combination provided.
0128     """
0129 
0130 class SSLFakeSocket:
0131     """A fake socket object that really wraps a SSLObject.
0132 
0133     It only supports what is needed in smtplib.
0134     """
0135     def __init__(self, realsock, sslobj):
0136         self.realsock = realsock
0137         self.sslobj = sslobj
0138 
0139     def send(self, str):
0140         self.sslobj.write(str)
0141         return len(str)
0142 
0143     sendall = send
0144 
0145     def close(self):
0146         self.realsock.close()
0147 
0148 class SSLFakeFile:
0149     """A fake file like object that really wraps a SSLObject.
0150 
0151     It only supports what is needed in smtplib.
0152     """
0153     def __init__( self, sslobj):
0154         self.sslobj = sslobj
0155 
0156     def readline(self):
0157         str = ""
0158         chr = None
0159         while chr != "\n":
0160             chr = self.sslobj.read(1)
0161             str += chr
0162         return str
0163 
0164     def close(self):
0165         pass
0166 
0167 def quoteaddr(addr):
0168     """Quote a subset of the email addresses defined by RFC 821.
0169 
0170     Should be able to handle anything rfc822.parseaddr can handle.
0171     """
0172     m = (None, None)
0173     try:
0174         m=rfc822.parseaddr(addr)[1]
0175     except AttributeError:
0176         pass
0177     if m == (None, None): # Indicates parse failure or AttributeError
0178         #something weird here.. punt -ddm
0179         return "<%s>" % addr
0180     else:
0181         return "<%s>" % m
0182 
0183 def quotedata(data):
0184     """Quote data for email.
0185 
0186     Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
0187     Internet CRLF end-of-line.
0188     """
0189     return re.sub(r'(?m)^\.', '..',
0190         re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
0191 
0192 
0193 class SMTP:
0194     """This class manages a connection to an SMTP or ESMTP server.
0195     SMTP Objects:
0196         SMTP objects have the following attributes:
0197             helo_resp
0198                 This is the message given by the server in response to the
0199                 most recent HELO command.
0200 
0201             ehlo_resp
0202                 This is the message given by the server in response to the
0203                 most recent EHLO command. This is usually multiline.
0204 
0205             does_esmtp
0206                 This is a True value _after you do an EHLO command_, if the
0207                 server supports ESMTP.
0208 
0209             esmtp_features
0210                 This is a dictionary, which, if the server supports ESMTP,
0211                 will _after you do an EHLO command_, contain the names of the
0212                 SMTP service extensions this server supports, and their
0213                 parameters (if any).
0214 
0215                 Note, all extension names are mapped to lower case in the
0216                 dictionary.
0217 
0218         See each method's docstrings for details.  In general, there is a
0219         method of the same name to perform each SMTP command.  There is also a
0220         method called 'sendmail' that will do an entire mail transaction.
0221         """
0222     debuglevel = 0
0223     file = None
0224     helo_resp = None
0225     ehlo_resp = None
0226     does_esmtp = 0
0227 
0228     def __init__(self, host = '', port = 0, local_hostname = None):
0229         """Initialize a new instance.
0230 
0231         If specified, `host' is the name of the remote host to which to
0232         connect.  If specified, `port' specifies the port to which to connect.
0233         By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised
0234         if the specified `host' doesn't respond correctly.  If specified,
0235         `local_hostname` is used as the FQDN of the local host.  By default,
0236         the local hostname is found using socket.getfqdn().
0237 
0238         """
0239         self.esmtp_features = {}
0240         if host:
0241             (code, msg) = self.connect(host, port)
0242             if code != 220:
0243                 raise SMTPConnectError(code, msg)
0244         if local_hostname is not None:
0245             self.local_hostname = local_hostname
0246         else:
0247             # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
0248             # if that can't be calculated, that we should use a domain literal
0249             # instead (essentially an encoded IP address like [A.B.C.D]).
0250             fqdn = socket.getfqdn()
0251             if '.' in fqdn:
0252                 self.local_hostname = fqdn
0253             else:
0254                 # We can't find an fqdn hostname, so use a domain literal
0255                 addr = socket.gethostbyname(socket.gethostname())
0256                 self.local_hostname = '[%s]' % addr
0257 
0258     def set_debuglevel(self, debuglevel):
0259         """Set the debug output level.
0260 
0261         A non-false value results in debug messages for connection and for all
0262         messages sent to and received from the server.
0263 
0264         """
0265         self.debuglevel = debuglevel
0266 
0267     def connect(self, host='localhost', port = 0):
0268         """Connect to a host on a given port.
0269 
0270         If the hostname ends with a colon (`:') followed by a number, and
0271         there is no port specified, that suffix will be stripped off and the
0272         number interpreted as the port number to use.
0273 
0274         Note: This method is automatically invoked by __init__, if a host is
0275         specified during instantiation.
0276 
0277         """
0278         if not port and (host.find(':') == host.rfind(':')):
0279             i = host.rfind(':')
0280             if i >= 0:
0281                 host, port = host[:i], host[i+1:]
0282                 try: port = int(port)
0283                 except ValueError:
0284                     raise socket.error, "nonnumeric port"
0285         if not port: port = SMTP_PORT
0286         if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
0287         msg = "getaddrinfo returns an empty list"
0288         self.sock = None
0289         for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
0290             af, socktype, proto, canonname, sa = res
0291             try:
0292                 self.sock = socket.socket(af, socktype, proto)
0293                 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
0294                 self.sock.connect(sa)
0295             except socket.error, msg:
0296                 if self.debuglevel > 0: print>>stderr, 'connect fail:', (host, port)
0297                 if self.sock:
0298                     self.sock.close()
0299                 self.sock = None
0300                 continue
0301             break
0302         if not self.sock:
0303             raise socket.error, msg
0304         (code, msg) = self.getreply()
0305         if self.debuglevel > 0: print>>stderr, "connect:", msg
0306         return (code, msg)
0307 
0308     def send(self, str):
0309         """Send `str' to the server."""
0310         if self.debuglevel > 0: print>>stderr, 'send:', repr(str)
0311         if self.sock:
0312             try:
0313                 self.sock.sendall(str)
0314             except socket.error:
0315                 self.close()
0316                 raise SMTPServerDisconnected('Server not connected')
0317         else:
0318             raise SMTPServerDisconnected('please run connect() first')
0319 
0320     def putcmd(self, cmd, args=""):
0321         """Send a command to the server."""
0322         if args == "":
0323             str = '%s%s' % (cmd, CRLF)
0324         else:
0325             str = '%s %s%s' % (cmd, args, CRLF)
0326         self.send(str)
0327 
0328     def getreply(self):
0329         """Get a reply from the server.
0330 
0331         Returns a tuple consisting of:
0332 
0333           - server response code (e.g. '250', or such, if all goes well)
0334             Note: returns -1 if it can't read response code.
0335 
0336           - server response string corresponding to response code (multiline
0337             responses are converted to a single, multiline string).
0338 
0339         Raises SMTPServerDisconnected if end-of-file is reached.
0340         """
0341         resp=[]
0342         if self.file is None:
0343             self.file = self.sock.makefile('rb')
0344         while 1:
0345             line = self.file.readline()
0346             if line == '':
0347                 self.close()
0348                 raise SMTPServerDisconnected("Connection unexpectedly closed")
0349             if self.debuglevel > 0: print>>stderr, 'reply:', repr(line)
0350             resp.append(line[4:].strip())
0351             code=line[:3]
0352             # Check that the error code is syntactically correct.
0353             # Don't attempt to read a continuation line if it is broken.
0354             try:
0355                 errcode = int(code)
0356             except ValueError:
0357                 errcode = -1
0358                 break
0359             # Check if multiline response.
0360             if line[3:4]!="-":
0361                 break
0362 
0363         errmsg = "\n".join(resp)
0364         if self.debuglevel > 0:
0365             print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
0366         return errcode, errmsg
0367 
0368     def docmd(self, cmd, args=""):
0369         """Send a command, and return its response code."""
0370         self.putcmd(cmd,args)
0371         return self.getreply()
0372 
0373     # std smtp commands
0374     def helo(self, name=''):
0375         """SMTP 'helo' command.
0376         Hostname to send for this command defaults to the FQDN of the local
0377         host.
0378         """
0379         self.putcmd("helo", name or self.local_hostname)
0380         (code,msg)=self.getreply()
0381         self.helo_resp=msg
0382         return (code,msg)
0383 
0384     def ehlo(self, name=''):
0385         """ SMTP 'ehlo' command.
0386         Hostname to send for this command defaults to the FQDN of the local
0387         host.
0388         """
0389         self.esmtp_features = {}
0390         self.putcmd("ehlo", name or self.local_hostname)
0391         (code,msg)=self.getreply()
0392         # According to RFC1869 some (badly written)
0393         # MTA's will disconnect on an ehlo. Toss an exception if
0394         # that happens -ddm
0395         if code == -1 and len(msg) == 0:
0396             self.close()
0397             raise SMTPServerDisconnected("Server not connected")
0398         self.ehlo_resp=msg
0399         if code != 250:
0400             return (code,msg)
0401         self.does_esmtp=1
0402         #parse the ehlo response -ddm
0403         resp=self.ehlo_resp.split('\n')
0404         del resp[0]
0405         for each in resp:
0406             # To be able to communicate with as many SMTP servers as possible,
0407             # we have to take the old-style auth advertisement into account,
0408             # because:
0409             # 1) Else our SMTP feature parser gets confused.
0410             # 2) There are some servers that only advertise the auth methods we
0411             #    support using the old style.
0412             auth_match = OLDSTYLE_AUTH.match(each)
0413             if auth_match:
0414                 # This doesn't remove duplicates, but that's no problem
0415                 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
0416                         + " " + auth_match.groups(0)[0]
0417                 continue
0418 
0419             # RFC 1869 requires a space between ehlo keyword and parameters.
0420             # It's actually stricter, in that only spaces are allowed between
0421             # parameters, but were not going to check for that here.  Note
0422             # that the space isn't present if there are no parameters.
0423             m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?',each)
0424             if m:
0425                 feature=m.group("feature").lower()
0426                 params=m.string[m.end("feature"):].strip()
0427                 if feature == "auth":
0428                     self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
0429                             + " " + params
0430                 else:
0431                     self.esmtp_features[feature]=params
0432         return (code,msg)
0433 
0434     def has_extn(self, opt):
0435         """Does the server support a given SMTP service extension?"""
0436         return opt.lower() in self.esmtp_features
0437 
0438     def help(self, args=''):
0439         """SMTP 'help' command.
0440         Returns help text from server."""
0441         self.putcmd("help", args)
0442         return self.getreply()
0443 
0444     def rset(self):
0445         """SMTP 'rset' command -- resets session."""
0446         return self.docmd("rset")
0447 
0448     def noop(self):
0449         """SMTP 'noop' command -- doesn't do anything :>"""
0450         return self.docmd("noop")
0451 
0452     def mail(self,sender,options=[]):
0453         """SMTP 'mail' command -- begins mail xfer session."""
0454         optionlist = ''
0455         if options and self.does_esmtp:
0456             optionlist = ' ' + ' '.join(options)
0457         self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
0458         return self.getreply()
0459 
0460     def rcpt(self,recip,options=[]):
0461         """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
0462         optionlist = ''
0463         if options and self.does_esmtp:
0464             optionlist = ' ' + ' '.join(options)
0465         self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
0466         return self.getreply()
0467 
0468     def data(self,msg):
0469         """SMTP 'DATA' command -- sends message data to server.
0470 
0471         Automatically quotes lines beginning with a period per rfc821.
0472         Raises SMTPDataError if there is an unexpected reply to the
0473         DATA command; the return value from this method is the final
0474         response code received when the all data is sent.
0475         """
0476         self.putcmd("data")
0477         (code,repl)=self.getreply()
0478         if self.debuglevel >0 : print>>stderr, "data:", (code,repl)
0479         if code != 354:
0480             raise SMTPDataError(code,repl)
0481         else:
0482             q = quotedata(msg)
0483             if q[-2:] != CRLF:
0484                 q = q + CRLF
0485             q = q + "." + CRLF
0486             self.send(q)
0487             (code,msg)=self.getreply()
0488             if self.debuglevel >0 : print>>stderr, "data:", (code,msg)
0489             return (code,msg)
0490 
0491     def verify(self, address):
0492         """SMTP 'verify' command -- checks for address validity."""
0493         self.putcmd("vrfy", quoteaddr(address))
0494         return self.getreply()
0495     # a.k.a.
0496     vrfy=verify
0497 
0498     def expn(self, address):
0499         """SMTP 'verify' command -- checks for address validity."""
0500         self.putcmd("expn", quoteaddr(address))
0501         return self.getreply()
0502 
0503     # some useful methods
0504 
0505     def login(self, user, password):
0506         """Log in on an SMTP server that requires authentication.
0507 
0508         The arguments are:
0509             - user:     The user name to authenticate with.
0510             - password: The password for the authentication.
0511 
0512         If there has been no previous EHLO or HELO command this session, this
0513         method tries ESMTP EHLO first.
0514 
0515         This method will return normally if the authentication was successful.
0516 
0517         This method may raise the following exceptions:
0518 
0519          SMTPHeloError            The server didn't reply properly to
0520                                   the helo greeting.
0521          SMTPAuthenticationError  The server didn't accept the username/
0522                                   password combination.
0523          SMTPException            No suitable authentication method was
0524                                   found.
0525         """
0526 
0527         def encode_cram_md5(challenge, user, password):
0528             challenge = base64.decodestring(challenge)
0529             response = user + " " + hmac.HMAC(password, challenge).hexdigest()
0530             return encode_base64(response, eol="")
0531 
0532         def encode_plain(user, password):
0533             return encode_base64("%s\0%s\0%s" % (user, user, password), eol="")
0534 
0535 
0536         AUTH_PLAIN = "PLAIN"
0537         AUTH_CRAM_MD5 = "CRAM-MD5"
0538         AUTH_LOGIN = "LOGIN"
0539 
0540         if self.helo_resp is None and self.ehlo_resp is None:
0541             if not (200 <= self.ehlo()[0] <= 299):
0542                 (code, resp) = self.helo()
0543                 if not (200 <= code <= 299):
0544                     raise SMTPHeloError(code, resp)
0545 
0546         if not self.has_extn("auth"):
0547             raise SMTPException("SMTP AUTH extension not supported by server.")
0548 
0549         # Authentication methods the server supports:
0550         authlist = self.esmtp_features["auth"].split()
0551 
0552         # List of authentication methods we support: from preferred to
0553         # less preferred methods. Except for the purpose of testing the weaker
0554         # ones, we prefer stronger methods like CRAM-MD5:
0555         preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
0556 
0557         # Determine the authentication method we'll use
0558         authmethod = None
0559         for method in preferred_auths:
0560             if method in authlist:
0561                 authmethod = method
0562                 break
0563 
0564         if authmethod == AUTH_CRAM_MD5:
0565             (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
0566             if code == 503:
0567                 # 503 == 'Error: already authenticated'
0568                 return (code, resp)
0569             (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
0570         elif authmethod == AUTH_PLAIN:
0571             (code, resp) = self.docmd("AUTH",
0572                 AUTH_PLAIN + " " + encode_plain(user, password))
0573         elif authmethod == AUTH_LOGIN:
0574             (code, resp) = self.docmd("AUTH",
0575                 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
0576             if code != 334:
0577                 raise SMTPAuthenticationError(code, resp)
0578             (code, resp) = self.docmd(encode_base64(password, eol=""))
0579         elif authmethod is None:
0580             raise SMTPException("No suitable authentication method found.")
0581         if code not in [235, 503]:
0582             # 235 == 'Authentication successful'
0583             # 503 == 'Error: already authenticated'
0584             raise SMTPAuthenticationError(code, resp)
0585         return (code, resp)
0586 
0587     def starttls(self, keyfile = None, certfile = None):
0588         """Puts the connection to the SMTP server into TLS mode.
0589 
0590         If the server supports TLS, this will encrypt the rest of the SMTP
0591         session. If you provide the keyfile and certfile parameters,
0592         the identity of the SMTP server and client can be checked. This,
0593         however, depends on whether the socket module really checks the
0594         certificates.
0595         """
0596         (resp, reply) = self.docmd("STARTTLS")
0597         if resp == 220:
0598             sslobj = socket.ssl(self.sock, keyfile, certfile)
0599             self.sock = SSLFakeSocket(self.sock, sslobj)
0600             self.file = SSLFakeFile(sslobj)
0601         return (resp, reply)
0602 
0603     def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
0604                  rcpt_options=[]):
0605         """This command performs an entire mail transaction.
0606 
0607         The arguments are:
0608             - from_addr    : The address sending this mail.
0609             - to_addrs     : A list of addresses to send this mail to.  A bare
0610                              string will be treated as a list with 1 address.
0611             - msg          : The message to send.
0612             - mail_options : List of ESMTP options (such as 8bitmime) for the
0613                              mail command.
0614             - rcpt_options : List of ESMTP options (such as DSN commands) for
0615                              all the rcpt commands.
0616 
0617         If there has been no previous EHLO or HELO command this session, this
0618         method tries ESMTP EHLO first.  If the server does ESMTP, message size
0619         and each of the specified options will be passed to it.  If EHLO
0620         fails, HELO will be tried and ESMTP options suppressed.
0621 
0622         This method will return normally if the mail is accepted for at least
0623         one recipient.  It returns a dictionary, with one entry for each
0624         recipient that was refused.  Each entry contains a tuple of the SMTP
0625         error code and the accompanying error message sent by the server.
0626 
0627         This method may raise the following exceptions:
0628 
0629          SMTPHeloError          The server didn't reply properly to
0630                                 the helo greeting.
0631          SMTPRecipientsRefused  The server rejected ALL recipients
0632                                 (no mail was sent).
0633          SMTPSenderRefused      The server didn't accept the from_addr.
0634          SMTPDataError          The server replied with an unexpected
0635                                 error code (other than a refusal of
0636                                 a recipient).
0637 
0638         Note: the connection will be open even after an exception is raised.
0639 
0640         Example:
0641 
0642          >>> import smtplib
0643          >>> s=smtplib.SMTP("localhost")
0644          >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
0645          >>> msg = '''\\
0646          ... From: Me@my.org
0647          ... Subject: testin'...
0648          ...
0649          ... This is a test '''
0650          >>> s.sendmail("me@my.org",tolist,msg)
0651          { "three@three.org" : ( 550 ,"User unknown" ) }
0652          >>> s.quit()
0653 
0654         In the above example, the message was accepted for delivery to three
0655         of the four addresses, and one was rejected, with the error code
0656         550.  If all addresses are accepted, then the method will return an
0657         empty dictionary.
0658 
0659         """
0660         if self.helo_resp is None and self.ehlo_resp is None:
0661             if not (200 <= self.ehlo()[0] <= 299):
0662                 (code,resp) = self.helo()
0663                 if not (200 <= code <= 299):
0664                     raise SMTPHeloError(code, resp)
0665         esmtp_opts = []
0666         if self.does_esmtp:
0667             # Hmmm? what's this? -ddm
0668             # self.esmtp_features['7bit']=""
0669             if self.has_extn('size'):
0670                 esmtp_opts.append("size=%d" % len(msg))
0671             for option in mail_options:
0672                 esmtp_opts.append(option)
0673 
0674         (code,resp) = self.mail(from_addr, esmtp_opts)
0675         if code != 250:
0676             self.rset()
0677             raise SMTPSenderRefused(code, resp, from_addr)
0678         senderrs={}
0679         if isinstance(to_addrs, basestring):
0680             to_addrs = [to_addrs]
0681         for each in to_addrs:
0682             (code,resp)=self.rcpt(each, rcpt_options)
0683             if (code != 250) and (code != 251):
0684                 senderrs[each]=(code,resp)
0685         if len(senderrs)==len(to_addrs):
0686             # the server refused all our recipients
0687             self.rset()
0688             raise SMTPRecipientsRefused(senderrs)
0689         (code,resp) = self.data(msg)
0690         if code != 250:
0691             self.rset()
0692             raise SMTPDataError(code, resp)
0693         #if we got here then somebody got our mail
0694         return senderrs
0695 
0696 
0697     def close(self):
0698         """Close the connection to the SMTP server."""
0699         if self.file:
0700             self.file.close()
0701         self.file = None
0702         if self.sock:
0703             self.sock.close()
0704         self.sock = None
0705 
0706 
0707     def quit(self):
0708         """Terminate the SMTP session."""
0709         self.docmd("quit")
0710         self.close()
0711 
0712 
0713 # Test the sendmail method, which tests most of the others.
0714 # Note: This always sends to localhost.
0715 if __name__ == '__main__':
0716     import sys
0717 
0718     def prompt(prompt):
0719         sys.stdout.write(prompt + ": ")
0720         return sys.stdin.readline().strip()
0721 
0722     fromaddr = prompt("From")
0723     toaddrs  = prompt("To").split(',')
0724     print "Enter message, end with ^D:"
0725     msg = ''
0726     while 1:
0727         line = sys.stdin.readline()
0728         if not line:
0729             break
0730         msg = msg + line
0731     print "Message length is %d" % len(msg)
0732 
0733     server = SMTP('localhost')
0734     server.set_debuglevel(1)
0735     server.sendmail(fromaddr, toaddrs, msg)
0736     server.quit()
0737 

Generated by PyXR 0.9.4
SourceForge.net Logo