📄 smtplib.py
字号:
if name: self.putcmd("helo", name) else: self.putcmd("helo", socket.getfqdn()) (code,msg)=self.getreply() self.helo_resp=msg return (code,msg) def ehlo(self, name=''): """ SMTP 'ehlo' command. Hostname to send for this command defaults to the FQDN of the local host. """ self.esmtp_features = {} if name: self.putcmd("ehlo", name) else: self.putcmd("ehlo", socket.getfqdn()) (code,msg)=self.getreply() # According to RFC1869 some (badly written) # MTA's will disconnect on an ehlo. Toss an exception if # that happens -ddm if code == -1 and len(msg) == 0: self.close() raise SMTPServerDisconnected("Server not connected") self.ehlo_resp=msg if code != 250: return (code,msg) self.does_esmtp=1 #parse the ehlo response -ddm resp=self.ehlo_resp.split('\n') del resp[0] for each in resp: # To be able to communicate with as many SMTP servers as possible, # we have to take the old-style auth advertisement into account, # because: # 1) Else our SMTP feature parser gets confused. # 2) There are some servers that only advertise the auth methods we # support using the old style. auth_match = OLDSTYLE_AUTH.match(each) if auth_match: # This doesn't remove duplicates, but that's no problem self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ + " " + auth_match.groups(0)[0] continue # RFC 1869 requires a space between ehlo keyword and parameters. # It's actually stricter, in that only spaces are allowed between # parameters, but were not going to check for that here. Note # that the space isn't present if there are no parameters. m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each) if m: feature=m.group("feature").lower() params=m.string[m.end("feature"):].strip() if feature == "auth": self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ + " " + params else: self.esmtp_features[feature]=params return (code,msg) def has_extn(self, opt): """Does the server support a given SMTP service extension?""" return self.esmtp_features.has_key(opt.lower()) def help(self, args=''): """SMTP 'help' command. Returns help text from server.""" self.putcmd("help", args) return self.getreply() def rset(self): """SMTP 'rset' command -- resets session.""" return self.docmd("rset") def noop(self): """SMTP 'noop' command -- doesn't do anything :>""" return self.docmd("noop") def mail(self,sender,options=[]): """SMTP 'mail' command -- begins mail xfer session.""" optionlist = '' if options and self.does_esmtp: optionlist = ' ' + ' '.join(options) self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist)) return self.getreply() def rcpt(self,recip,options=[]): """SMTP 'rcpt' command -- indicates 1 recipient for this mail.""" optionlist = '' if options and self.does_esmtp: optionlist = ' ' + ' '.join(options) self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist)) return self.getreply() def data(self,msg): """SMTP 'DATA' command -- sends message data to server. Automatically quotes lines beginning with a period per rfc821. Raises SMTPDataError if there is an unexpected reply to the DATA command; the return value from this method is the final response code received when the all data is sent. """ self.putcmd("data") (code,repl)=self.getreply() if self.debuglevel >0 : print "data:", (code,repl) if code != 354: raise SMTPDataError(code,repl) else: q = quotedata(msg) if q[-2:] != CRLF: q = q + CRLF q = q + "." + CRLF self.send(q) (code,msg)=self.getreply() if self.debuglevel >0 : print "data:", (code,msg) return (code,msg) def verify(self, address): """SMTP 'verify' command -- checks for address validity.""" self.putcmd("vrfy", quoteaddr(address)) return self.getreply() # a.k.a. vrfy=verify def expn(self, address): """SMTP 'verify' command -- checks for address validity.""" self.putcmd("expn", quoteaddr(address)) return self.getreply() # some useful methods def login(self, user, password): """Log in on an SMTP server that requires authentication. The arguments are: - user: The user name to authenticate with. - password: The password for the authentication. If there has been no previous EHLO or HELO command this session, this method tries ESMTP EHLO first. This method will return normally if the authentication was successful. This method may raise the following exceptions: SMTPHeloError The server didn't reply properly to the helo greeting. SMTPAuthenticationError The server didn't accept the username/ password combination. SMTPException No suitable authentication method was found. """ def encode_cram_md5(challenge, user, password): challenge = base64.decodestring(challenge) response = user + " " + hmac.HMAC(password, challenge).hexdigest() return encode_base64(response, eol="") def encode_plain(user, password): return encode_base64("%s\0%s\0%s" % (user, user, password), eol="") AUTH_PLAIN = "PLAIN" AUTH_CRAM_MD5 = "CRAM-MD5" AUTH_LOGIN = "LOGIN" if self.helo_resp is None and self.ehlo_resp is None: if not (200 <= self.ehlo()[0] <= 299): (code, resp) = self.helo() if not (200 <= code <= 299): raise SMTPHeloError(code, resp) if not self.has_extn("auth"): raise SMTPException("SMTP AUTH extension not supported by server.") # Authentication methods the server supports: authlist = self.esmtp_features["auth"].split() # List of authentication methods we support: from preferred to # less preferred methods. Except for the purpose of testing the weaker # ones, we prefer stronger methods like CRAM-MD5: preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN] # Determine the authentication method we'll use authmethod = None for method in preferred_auths: if method in authlist: authmethod = method break if authmethod == AUTH_CRAM_MD5: (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5) if code == 503: # 503 == 'Error: already authenticated' return (code, resp) (code, resp) = self.docmd(encode_cram_md5(resp, user, password)) elif authmethod == AUTH_PLAIN: (code, resp) = self.docmd("AUTH", AUTH_PLAIN + " " + encode_plain(user, password)) elif authmethod == AUTH_LOGIN: (code, resp) = self.docmd("AUTH", "%s %s" % (AUTH_LOGIN, encode_base64(user, eol=""))) if code != 334: raise SMTPAuthenticationError(code, resp) (code, resp) = self.docmd(encode_base64(password, eol="")) elif authmethod == None: raise SMTPException("No suitable authentication method found.") if code not in [235, 503]: # 235 == 'Authentication successful' # 503 == 'Error: already authenticated' raise SMTPAuthenticationError(code, resp) return (code, resp) def starttls(self, keyfile = None, certfile = None): """Puts the connection to the SMTP server into TLS mode. If the server supports TLS, this will encrypt the rest of the SMTP session. If you provide the keyfile and certfile parameters, the identity of the SMTP server and client can be checked. This, however, depends on whether the socket module really checks the certificates. """ (resp, reply) = self.docmd("STARTTLS") if resp == 220: sslobj = socket.ssl(self.sock, keyfile, certfile) self.sock = SSLFakeSocket(self.sock, sslobj) self.file = SSLFakeFile(sslobj) return (resp, reply) def sendmail(self, from_addr, to_addrs, msg, mail_options=[], rcpt_options=[]): """This command performs an entire mail transaction. The arguments are: - from_addr : The address sending this mail. - to_addrs : A list of addresses to send this mail to. A bare string will be treated as a list with 1 address. - msg : The message to send. - mail_options : List of ESMTP options (such as 8bitmime) for the mail command. - rcpt_options : List of ESMTP options (such as DSN commands) for all the rcpt commands. If there has been no previous EHLO or HELO command this session, this method tries ESMTP EHLO first. If the server does ESMTP, message size and each of the specified options will be passed to it. If EHLO fails, HELO will be tried and ESMTP options suppressed. This method will return normally if the mail is accepted for at least one recipient. It returns a dictionary, with one entry for each recipient that was refused. Each entry contains a tuple of the SMTP error code and the accompanying error message sent by the server. This method may raise the following exceptions: SMTPHeloError The server didn't reply properly to the helo greeting. SMTPRecipientsRefused The server rejected ALL recipients (no mail was sent). SMTPSenderRefused The server didn't accept the from_addr. SMTPDataError The server replied with an unexpected error code (other than a refusal of a recipient). Note: the connection will be open even after an exception is raised. Example: >>> import smtplib >>> s=smtplib.SMTP("localhost") >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] >>> msg = '''\\ ... From: Me@my.org ... Subject: testin'... ... ... This is a test ''' >>> s.sendmail("me@my.org",tolist,msg) { "three@three.org" : ( 550 ,"User unknown" ) } >>> s.quit() In the above example, the message was accepted for delivery to three of the four addresses, and one was rejected, with the error code 550. If all addresses are accepted, then the method will return an empty dictionary. """ if self.helo_resp is None and self.ehlo_resp is None: if not (200 <= self.ehlo()[0] <= 299): (code,resp) = self.helo() if not (200 <= code <= 299): raise SMTPHeloError(code, resp) esmtp_opts = [] if self.does_esmtp: # Hmmm? what's this? -ddm # self.esmtp_features['7bit']="" if self.has_extn('size'): esmtp_opts.append("size=" + `len(msg)`) for option in mail_options: esmtp_opts.append(option) (code,resp) = self.mail(from_addr, esmtp_opts) if code != 250: self.rset() raise SMTPSenderRefused(code, resp, from_addr) senderrs={} if isinstance(to_addrs, types.StringTypes): to_addrs = [to_addrs] for each in to_addrs: (code,resp)=self.rcpt(each, rcpt_options) if (code != 250) and (code != 251): senderrs[each]=(code,resp) if len(senderrs)==len(to_addrs): # the server refused all our recipients self.rset() raise SMTPRecipientsRefused(senderrs) (code,resp) = self.data(msg) if code != 250: self.rset() raise SMTPDataError(code, resp) #if we got here then somebody got our mail return senderrs def close(self): """Close the connection to the SMTP server.""" if self.file: self.file.close() self.file = None if self.sock: self.sock.close() self.sock = None def quit(self): """Terminate the SMTP session.""" self.docmd("quit") self.close()# Test the sendmail method, which tests most of the others.# Note: This always sends to localhost.if __name__ == '__main__': import sys def prompt(prompt): sys.stdout.write(prompt + ": ") return sys.stdin.readline().strip() fromaddr = prompt("From") toaddrs = prompt("To").split(',') print "Enter message, end with ^D:" msg = '' while 1: line = sys.stdin.readline() if not line: break msg = msg + line print "Message length is " + `len(msg)` server = SMTP('localhost') server.set_debuglevel(1) server.sendmail(fromaddr, toaddrs, msg) server.quit()
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -