Exemple #1
0
 def smtp_MAIL(self, args):
     if not self._client:
         self.send_line('503 Error: out of sequence command - no HELO')
         return
     fromaddr = self.get_address(args)
     self.current = Envelope(fromaddr)
     self.current.write("Received: from %s (%s) by %s with SMTP ; %s\r\n" % (self._client, self.otheraddr[0], self.hostname, formatdate()))
     self.envelopes.append(self.current)
     self.send_line("250 sender <%r> ok" % fromaddr)
Exemple #2
0
 def get_message(self, timeout=60):
     """returns an Envelope object from the server."""
     if self._server:
         # get envelope (with message) and stash its conversation and client
         # address in it.
         envelope = self._server.poll(timeout, [])
         if envelope is not None:
             envelope.conversation = self._server.get_conversation()
             envelope.otheraddress = self._server.otheraddr
             return envelope
         else:
             envelope = Envelope() # return empty one then, to provide consistent interface.
             envelope.otheraddress = None
             envelope.conversation = self._server.get_conversation()
             return envelope
Exemple #3
0
 def get_message(self, timeout=60):
     """returns an Envelope object from the server."""
     if self._server:
         # get envelope (with message) and stash its conversation and client
         # address in it.
         envelope = self._server.poll(timeout, [])
         if envelope is not None:
             envelope.conversation = self._server.get_conversation()
             envelope.otheraddress = self._server.otheraddr
             return envelope
         else:
             envelope = Envelope() # return empty one then, to provide consistent interface.
             envelope.otheraddress = None
             envelope.conversation = self._server.get_conversation()
             return envelope
Exemple #4
0
 def smtp_MAIL(self, args):
     if not self._client:
         self.send_line('503 Error: out of sequence command - no HELO')
         return
     fromaddr = self.get_address(args)
     self.current = Envelope(fromaddr)
     self.current.write("Received: from %s (%s) by %s with SMTP ; %s\r\n" % (self._client, self.otheraddr[0], self.hostname, formatdate()))
     self.envelopes.append(self.current)
     self.send_line("250 sender <%r> ok" % fromaddr)
Exemple #5
0
class SMTPServer(object):
    """SMTPDServer(port=25, logfile=None, parser=None)
An SMTP server object that listens on the specified port. If a 'logfile'
file-like object is supplied the SMTP conversation will be written to it.
If a parser object is supplied then any message body recieved will be
automatically parsed, and any errors returned to the client.  """
    def __init__(self, port=9025, logfile=None, parser=None):
        self.port = port
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.file = None
        self.conn = None
        self.envelopes = []
        self.hostname = socket.gethostname()
        self._client = None
        self._callback = None
        self._reset()
        while 1:
            try:
                self.socket.bind(("", self.port))
                break
            except socket.error as msg:
                print("couldn't bind port",
                      self.port,
                      " - ",
                      msg,
                      "retrying...",
                      file=sys.stderr)
            scheduler.sleep(5)
        self.socket.listen(50)
        self.conversation = None
        self.logfile = logfile
        self.parser = parser

    def __del__(self):
        self.close()

    def _reset(self):
        self._state = 1
        self.current = None

    def poll(self, timeout=0, conversation=None):
        """poll(timeout=0, conversation=None)
Polls the server, waiting *timeout* seconds (or forever if zero). Returns
one message Envelope object if available.
"""
        self.accept(timeout)
        self.smtp_conversation(conversation)
        if self.envelopes:
            return self.envelopes.pop()
        else:
            return None

    def run(self, callback=None, timeout=0, conversation=None):
        """run(cb=None, timeout=0, conversation=None)
Runs the SMTP server (single thread). If a list object is passed in as the
'conversation' then the SMTP conversation will be stored in it.  If a
callback function is supplied then it will be called for each message
Envelope recieved (with the Envelope object as a parameter)."""
        self._callback = callback
        while 1:
            self.accept(timeout)
            self.smtp_conversation(conversation)

    def get_envelopes(self):
        """get_envelopes()
Returns the stored list of Envelope objects, clearing the list."""
        elist = self.envelopes
        self.envelopes = []
        return elist

    def get_conversation(self):
        """get_conversation()
Fetches the conversation list-object from this server."""
        conversation = self.conversation
        self.conversation = None
        return conversation

    matchaddr = re.compile(b".*<(.*)>.*")

    def get_address(self, line):
        match = self.matchaddr.match(line)
        if not match:
            return None
        return match.group(1)

    def accept(self, timeout=60):
        tries = timeout / 6
        count = 0
        self.socket.setblocking(0)
        while 1:
            try:
                conn, self.otheraddr = self.socket.accept()
            except socket.error as why:
                if why.errno == EINTR:
                    continue
                if why.errno == EAGAIN:
                    if timeout > 0 and count > tries:
                        self.socket.setblocking(1)
                        raise TimeoutError("did not accept() in time.")
                    count += 1
                    scheduler.sleep(6)
                    continue
                else:
                    raise
            else:
                break
        self.socket.setblocking(1)
        conn.setblocking(1)
        self.file = conn.makefile()
        self.conn = conn
        return self.file

    def close(self):
        """close()
Closes the server (stops listening)."""
        if self.socket:
            self.socket.close()
            self.socket = None
        if self.file:
            self.file.close()
            self.file = None
        if self.conn:
            self.conn.close()
            self.conn = None

    def conn_close(self):
        self.conn.close()
        self.file.close()
        self.conn = None
        self.file = None
        self._client = None
        self._reset()

    def readline(self):
        while 1:
            try:
                line = self.file.readline()
            except EnvironmentError as why:
                if why.errno == EINTR:
                    continue
                else:
                    raise
            else:
                break
        if not line:
            return None, None
        if self.conversation is not None:
            self.conversation.append(("RECV", line))
        if self.logfile:
            self.logfile.write("RECV: %s" % (line, ))
        line = line.strip()
        parts = line.split(None, 1)
        command = parts[0]
        args = ''
        if len(parts) > 1:
            args = parts[1]
        return command, args

    def send_line(self, line):
        if self.conversation is not None:
            self.conversation.append(("SENT", line))
        if self.logfile:
            self.logfile.write(("SENT: %s\n" % (line, )))
        try:
            self.conn.send((line + CRLF).encode("ascii"))
        except IOError:
            raise ConversationOverException()

    def smtp_conversation(self, conversation=None):
        self.conversation = conversation  # should be a list object
        self.send_line("220 IAF.smtpd4testing (hello from %r) ESMTP" %
                       (self.otheraddr, ))
        while 1:
            try:
                command, args = self.readline()
            except IOError:
                self.conn_close()
                break
            if not command:
                self.conn_close()
                break
            method = getattr(self, 'smtp_' + command.decode("ascii").upper(),
                             None)
            if not method:
                self.send_line('500 Error: command "%s" not implemented' %
                               command)
                print("no handler for command '%s'" % command, file=sys.stderr)
                continue
            try:
                method(args)
            except ConversationOverException:
                self.conn_close()
                break
            except DataFinish:
                if self._callback and self.envelopes:
                    self._callback(self.envelopes.pop())

    def smtp_HELO(self, args):
        self._client = args
        self.send_line("250 IAF.smtpd4testing")

    def smtp_EHLO(self, args):
        self._client = args
        self.send_line("250-IAF")
        self.send_line("250 SIZE 10485760")

    def smtp_MAIL(self, args):
        if not self._client:
            self.send_line('503 Error: out of sequence command - no HELO')
            return
        fromaddr = self.get_address(args)
        self.current = Envelope(fromaddr)
        self.current.write(
            "Received: from %s (%s) by %s with SMTP ; %s\r\n" %
            (self._client, self.otheraddr[0], self.hostname, formatdate()))
        self.envelopes.append(self.current)
        self.send_line("250 sender <%r> ok" % fromaddr)

    def smtp_RCPT(self, args):
        if self.current is None or not self.current.mail_from:
            self.send_line('503 Error: out of sequence command')
            return
        rcpt = self.get_address(args)
        if not rcpt:
            self.send_line("501 need recipient")
            return
        self.current.add_rcpt(rcpt)
        self.send_line("250 recipient <%r> ok" % rcpt)

    def smtp_QUIT(self, args):
        if args:
            self.send_line('501 Syntax: QUIT')
            return
        self.send_line("221 %s closing channel" % (self.hostname, ))
        raise ConversationOverException()

    def smtp_DATA(self, args):
        if self.current is None or not self.current.mail_from:
            self.send_line('503 Error: out of sequence command')
            return
        if not self.current.rcpt_to:
            self.send_line('554 Error: need RCPT command')
            return
        self.send_line("354 feed me")
        self._state = 2
        while 1:
            line = self.file.readline().decode("ascii")
            if self.logfile:
                self.logfile.write(line)
            if line == '.\r\n':
                try:
                    if self.parser:
                        self.current.parse_data(self.parser)
                    self._reset()
                    self.send_line("250 OK")
                except:  # parser error
                    ex, val, tb = sys.exc_info()
                    print(ex, val, file=sys.stderr)
                    self._reset()
                    self.send_line("451 %s (%s)" % (ex, val))
                    break
                raise DataFinish()
            self.current.writeln(line.rstrip(CRLF))

    def smtp_RSET(self, args):
        if args:
            self.send_line('501 Syntax: RSET')
            return
        self._reset()
        self.send_line('250 OK')

    def smtp_NOOP(self, arg):
        self.send_line('250 OK')

    def smtp_VRFY(self, arg):
        self.send_line("502 not implemented")

    def smtp_EXPN(self, arg):
        self.send_line("502 not implemented")

    def smtp_HELP(self, arg):
        self.send_line("502 not implemented")
Exemple #6
0
class SMTPServer(object):
    """SMTPDServer(port=25, logfile=None, parser=None)
An SMTP server object that listens on the specified port. If a 'logfile'
file-like object is supplied the SMTP conversation will be written to it.
If a parser object is supplied then any message body recieved will be
automatically parsed, and any errors returned to the client.  """
    def __init__ (self, port=9025, logfile=None, parser=None):
        self.port = port
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.file = None
        self.conn = None
        self.envelopes = []
        self.hostname = socket.gethostname()
        self._client = None
        self._callback = None
        self._reset()
        while 1:
            try:
                self.socket.bind(("", self.port))
                break
            except socket.error as msg:
                print ("couldn't bind port",self.port," - ",msg,"retrying...", file=sys.stderr)
            scheduler.sleep(5)
        self.socket.listen(50)
        self.conversation = None
        self.logfile = logfile
        self.parser = parser

    def __del__(self):
        self.close()

    def _reset(self):
        self._state = 1
        self.current = None

    def poll(self, timeout=0, conversation=None):
        """poll(timeout=0, conversation=None)
Polls the server, waiting *timeout* seconds (or forever if zero). Returns
one message Envelope object if available.
"""
        self.accept(timeout)
        self.smtp_conversation(conversation)
        if self.envelopes:
            return self.envelopes.pop()
        else:
            return None

    def run(self, callback=None, timeout=0, conversation=None):
        """run(cb=None, timeout=0, conversation=None)
Runs the SMTP server (single thread). If a list object is passed in as the
'conversation' then the SMTP conversation will be stored in it.  If a
callback function is supplied then it will be called for each message
Envelope recieved (with the Envelope object as a parameter)."""
        self._callback = callback
        while 1:
            self.accept(timeout)
            self.smtp_conversation(conversation)

    def get_envelopes(self):
        """get_envelopes()
Returns the stored list of Envelope objects, clearing the list."""
        elist = self.envelopes
        self.envelopes = []
        return elist

    def get_conversation(self):
        """get_conversation()
Fetches the conversation list-object from this server."""
        conversation = self.conversation
        self.conversation = None
        return conversation

    matchaddr = re.compile (b".*<(.*)>.*")
    def get_address (self, line):
        match = self.matchaddr.match(line)
        if not match:
            return None
        return match.group(1)

    def accept(self, timeout=60):
        tries = timeout/6
        count = 0
        self.socket.setblocking(0)
        while 1:
            try:
                conn, self.otheraddr = self.socket.accept()
            except socket.error as why:
                if why.errno == EINTR:
                    continue
                if why.errno == EAGAIN:
                    if timeout > 0 and count > tries:
                        self.socket.setblocking(1)
                        raise TimeoutError("did not accept() in time.")
                    count += 1
                    scheduler.sleep(6)
                    continue
                else:
                    raise
            else:
                break
        self.socket.setblocking(1)
        conn.setblocking(1)
        self.file = conn.makefile()
        self.conn = conn
        return self.file

    def close(self):
        """close()
Closes the server (stops listening)."""
        if self.socket:
            self.socket.close()
            self.socket = None
        if self.file:
            self.file.close()
            self.file = None
        if self.conn:
            self.conn.close()
            self.conn = None

    def conn_close(self):
        self.conn.close()
        self.file.close()
        self.conn = None
        self.file = None
        self._client = None
        self._reset()

    def readline(self):
        while 1:
            try:
                line = self.file.readline()
            except EnvironmentError as why:
                if why.errno == EINTR:
                    continue
                else:
                    raise
            else:
                break
        if not line:
            return None, None
        if self.conversation is not None:
            self.conversation.append(("RECV", line))
        if self.logfile:
            self.logfile.write("RECV: %s" % (line,))
        line = line.strip()
        parts = line.split(None, 1)
        command = parts[0]
        args = ''
        if len(parts) > 1:
            args = parts[1]
        return command, args

    def send_line(self, line):
        if self.conversation is not None:
            self.conversation.append(("SENT", line))
        if self.logfile:
            self.logfile.write(("SENT: %s\n" % (line,)))
        try:
            self.conn.send((line + CRLF).encode("ascii"))
        except IOError:
            raise ConversationOverException()

    def smtp_conversation(self, conversation=None):
        self.conversation = conversation # should be a list object
        self.send_line("220 IAF.smtpd4testing (hello from %r) ESMTP" % (self.otheraddr,))
        while 1:
            try:
                command, args = self.readline()
            except IOError:
                self.conn_close()
                break
            if not command:
                self.conn_close()
                break
            method = getattr(self, 'smtp_' + command.decode("ascii").upper(), None)
            if not method:
                self.send_line('500 Error: command "%s" not implemented' % command)
                print ("no handler for command '%s'" % command, file=sys.stderr)
                continue
            try:
                method(args)
            except ConversationOverException:
                self.conn_close()
                break
            except DataFinish:
                if self._callback and self.envelopes:
                    self._callback(self.envelopes.pop())

    def smtp_HELO(self, args):
        self._client = args
        self.send_line ("250 IAF.smtpd4testing")

    def smtp_EHLO(self, args):
        self._client = args
        self.send_line("250-IAF")
        self.send_line("250 SIZE 10485760")

    def smtp_MAIL(self, args):
        if not self._client:
            self.send_line('503 Error: out of sequence command - no HELO')
            return
        fromaddr = self.get_address(args)
        self.current = Envelope(fromaddr)
        self.current.write("Received: from %s (%s) by %s with SMTP ; %s\r\n" % (self._client, self.otheraddr[0], self.hostname, formatdate()))
        self.envelopes.append(self.current)
        self.send_line("250 sender <%r> ok" % fromaddr)

    def smtp_RCPT(self, args):
        if self.current is None or not self.current.mail_from:
            self.send_line('503 Error: out of sequence command')
            return
        rcpt = self.get_address(args)
        if not rcpt:
            self.send_line("501 need recipient")
            return
        self.current.add_rcpt(rcpt)
        self.send_line("250 recipient <%r> ok" % rcpt)

    def smtp_QUIT(self, args):
        if args:
            self.send_line('501 Syntax: QUIT')
            return
        self.send_line("221 %s closing channel" % (self.hostname,))
        raise ConversationOverException()

    def smtp_DATA(self, args):
        if self.current is None or not self.current.mail_from:
            self.send_line('503 Error: out of sequence command')
            return
        if not self.current.rcpt_to:
            self.send_line('554 Error: need RCPT command')
            return
        self.send_line ("354 feed me")
        self._state = 2
        while 1:
            line = self.file.readline().decode("ascii")
            if self.logfile:
                self.logfile.write(line)
            if line == '.\r\n':
                try:
                    if self.parser:
                        self.current.parse_data(self.parser)
                    self._reset()
                    self.send_line("250 OK")
                except: # parser error
                    ex, val, tb = sys.exc_info()
                    print (ex, val, file=sys.stderr)
                    self._reset()
                    self.send_line("451 %s (%s)" % (ex, val))
                    break
                raise DataFinish()
            self.current.writeln(line.rstrip(CRLF))

    def smtp_RSET(self, args):
        if args:
            self.send_line('501 Syntax: RSET')
            return
        self._reset()
        self.send_line('250 OK')

    def smtp_NOOP(self, arg):
        self.send_line('250 OK')

    def smtp_VRFY(self, arg):
        self.send_line("502 not implemented")

    def smtp_EXPN(self, arg):
        self.send_line("502 not implemented")

    def smtp_HELP(self, arg):
        self.send_line("502 not implemented")