summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xLib/smtplib.py278
1 files changed, 278 insertions, 0 deletions
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
new file mode 100755
index 0000000..cf229f3
--- /dev/null
+++ b/Lib/smtplib.py
@@ -0,0 +1,278 @@
+"""SMTP Client class.
+
+Author: The Dragon De Monsyne <dragondm@integral.org>
+
+(This was modified from the Python 1.5 library HTTP lib.)
+
+This should follow RFC 821. (it dosen't do esmtp (yet))
+
+Example:
+
+>>> import smtplib
+>>> s=smtplib.SMTP("localhost")
+>>> print s.help()
+This is Sendmail version 8.8.4
+Topics:
+ HELO EHLO MAIL RCPT DATA
+ RSET NOOP QUIT HELP VRFY
+ EXPN VERB ETRN DSN
+For more info use "HELP <topic>".
+To report bugs in the implementation send email to
+ sendmail-bugs@sendmail.org.
+For local information send email to Postmaster at your site.
+End of HELP info
+>>> s.putcmd("vrfy","someone@here")
+>>> s.getreply()
+(250, "Somebody OverHere <somebody@here.my.org>")
+>>> s.quit()
+
+"""
+
+import socket
+import string,re
+
+SMTP_PORT = 25
+CRLF="\r\n"
+
+# used for exceptions
+SMTPSenderRefused="Sender address refused"
+SMTPRecipientsRefused="All Recipients refused"
+SMTPDataError="Error transmoitting message data"
+
+class SMTP:
+ """This class manages a connection to an SMTP server."""
+
+ def __init__(self, host = '', port = 0):
+ """Initialize a new instance.
+
+ If specified, `host' is the name of the remote host to which
+ to connect. If specified, `port' specifies the port to which
+ to connect. By default, smtplib.SMTP_PORT is used.
+
+ """
+ self.debuglevel = 0
+ self.file = None
+ self.helo_resp = None
+ if host: self.connect(host, port)
+
+ def set_debuglevel(self, debuglevel):
+ """Set the debug output level.
+
+ A non-false value results in debug messages for connection and
+ for all messages sent to and received from the server.
+
+ """
+ self.debuglevel = debuglevel
+
+ def connect(self, host='localhost', port = 0):
+ """Connect to a host on a given port.
+
+ Note: This method is automatically invoked by __init__,
+ if a host is specified during instantiation.
+
+ """
+ if not port:
+ i = string.find(host, ':')
+ if i >= 0:
+ host, port = host[:i], host[i+1:]
+ try: port = string.atoi(port)
+ except string.atoi_error:
+ raise socket.error, "nonnumeric port"
+ if not port: port = SMTP_PORT
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ if self.debuglevel > 0: print 'connect:', (host, port)
+ self.sock.connect(host, port)
+ (code,msg)=self.getreply()
+ if self.debuglevel >0 : print "connect:", msg
+ return msg
+
+ def send(self, str):
+ """Send `str' to the server."""
+ if self.debuglevel > 0: print 'send:', `str`
+ self.sock.send(str)
+
+ def putcmd(self, cmd, args=""):
+ """Send a command to the server.
+ """
+ str = '%s %s%s' % (cmd, args, CRLF)
+ self.send(str)
+
+ def getreply(self):
+ """Get a reply from the server.
+
+ Returns a tuple consisting of:
+ - server response code (e.g. '250', or such, if all goes well)
+ Note: returns -1 if it can't read responce code.
+ - server response string corresponding to response code
+ (note : multiline responces converted to a single, multiline
+ string)
+
+ """
+ resp=[]
+ self.file = self.sock.makefile('rb')
+ while 1:
+ line = self.file.readline()
+ if self.debuglevel > 0: print 'reply:', `line`
+ resp.append(string.strip(line[4:]))
+ code=line[:3]
+ #check if multiline resp
+ if line[3:4]!="-":
+ break
+ try:
+ errcode = string.atoi(code)
+ except(ValueError):
+ errcode = -1
+
+ errmsg = string.join(resp,"\n")
+ if self.debuglevel > 0:
+ print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
+ return errcode, errmsg
+
+ def docmd(self, cmd, args=""):
+ """ Send a command, and return it's responce code """
+
+ self.putcmd(cmd,args)
+ (code,msg)=self.getreply()
+ return code
+# std smtp commands
+
+ def helo(self, name=''):
+ """ SMTP 'helo' command. Hostname to send for this command
+ defaults to the FQDN of the local host """
+ name=string.strip(name)
+ if len(name)==0:
+ name=socket.gethostbyaddr(socket.gethostname())[0]
+ self.putcmd("helo",name)
+ (code,msg)=self.getreply()
+ self.helo_resp=msg
+ return code
+
+ def help(self):
+ """ SMTP 'help' command. Returns help text from server """
+ self.putcmd("help")
+ (code,msg)=self.getreply()
+ return msg
+
+ def rset(self):
+ """ SMTP 'rset' command. Resets session. """
+ code=self.docmd("rset")
+ return code
+
+ def noop(self):
+ """ SMTP 'noop' command. Dosen't do anything :> """
+ code=self.docmd("noop")
+ return code
+
+ def mail(self,sender):
+ """ SMTP 'mail' command. Begins mail xfer session. """
+ self.putcmd("mail","from: %s" % sender)
+ return self.getreply()
+
+ def rcpt(self,recip):
+ """ SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
+ self.putcmd("rcpt","to: %s" % recip)
+ return self.getreply()
+
+ def data(self,msg):
+ """ SMTP 'DATA' command. Sends message data to server.
+ Automatically quotes lines beginning with a period per rfc821 """
+ #quote periods in msg according to RFC821
+ # ps, I don't know why I have to do it this way... doing:
+ # quotepat=re.compile(r"^[.]",re.M)
+ # msg=re.sub(quotepat,"..",msg)
+ # should work, but it dosen't (it doubles the number of any
+ # contiguous series of .'s at the beginning of a line,
+ # instead of just adding one. )
+ quotepat=re.compile(r"^[.]+",re.M)
+ def m(pat):
+ return "."+pat.group(0)
+ msg=re.sub(quotepat,m,msg)
+ self.putcmd("data")
+ (code,repl)=self.getreply()
+ if self.debuglevel >0 : print "data:", (code,repl)
+ if code <> 354:
+ return -1
+ else:
+ self.send(msg)
+ self.send("\n.\n")
+ (code,msg)=self.getreply()
+ if self.debuglevel >0 : print "data:", (code,msg)
+ return code
+
+#some usefull methods
+ def sendmail(self,from_addr,to_addrs,msg):
+ """ 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
+ - msg : the message to send.
+
+ This method will return normally if the mail is accepted for at
+ least one recipiant .Otherwise it will throw an exception (either
+ SMTPSenderRefused,SMTPRecipientsRefused, or SMTPDataError)
+
+ That is, if this method does not throw an excception, then someone
+ should get your mail.
+
+ It returns a dictionary , with one entry for each recipient that
+ was refused.
+
+ 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 not self.helo_resp:
+ self.helo()
+ (code,resp)=self.mail(from_addr)
+ if code <>250:
+ self.rset()
+ raise SMTPSenderRefused
+ senderrs={}
+ for each in to_addrs:
+ (code,resp)=self.rcpt(each)
+ if (code <> 250) and (code <> 251):
+ senderr[each]=(code,resp)
+ if len(senderrs)==len(to_addrs):
+ #th' server refused all our recipients
+ self.rset()
+ raise SMTPRecipientsRefused
+ code=self.data(msg)
+ if code <>250 :
+ self.rset()
+ raise SMTPDataError
+ #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):
+ self.docmd("quit")
+ self.close()