def smtp_RCPT(self, arg): event.ReceivedSMTPRcpt.notify() print('===> RCPT', arg, file = debug.stream()) # protocol validation (rfc8321) if not self.transaction: self.sendReply('503 Error: need MAIL command', event.ReceivedSMTPRcpt) return address = self.__getaddr('TO:', arg) if arg else None if not address: self.sendReply('501 Syntax: RCPT TO: <address>', event.ReceivedSMTPRcpt) return # target smtp validation try: (code, reply) = self.server.rcpt(address) if 250 != code: # TODO: maybe examine reply? self.sendReply((code, reply), event.ReceivedSMTPRcpt) return except Exception: self.sendReply('451 Error in processing', event.ReceivedSMTPRcpt) return # end validation result = sfsp.plugin.filter.Filter.validateRecipient(address) if 250 != result.mainresult.smtp_error: self.sendReply((result.mainresult.smtp_error, result.mainresult.message), event.ReceivedSMTPRcpt) return # end filtering self.transaction.addRecipient(address) print('recips:', self.transaction.recipients, file = debug.stream()) self.sendOK(event.ReceivedSMTPRcpt)
def smtp_MAIL(self, arg): event.ReceivedSMTPMail.notify() print('===> MAIL', arg, file=debug.stream()) # protocol validation (rfc8321) if not self.seen_greeting: self.sendReply('503 Error: MAIL before (EH|HE)LO', event.SendSMTPMailResponse) return if self.transaction: self.sendReply('503 Error: nested MAIL command', event.SendSMTPMailResponse) return address = self.__getaddr('FROM:', arg) if arg else None if not address: self.sendReply('501 Syntax: MAIL FROM:<address>', event.SendSMTPMailResponse) return # target smtp validation try: (code, reply) = self.server.connect_if_needed( self.sfsp.options.remoteaddress, self.sfsp.options.remoteport) if 220 != code and 250 != code: # TODO: maybe only forward 421? self.sendReply((code, reply), event.SendSMTPMailResponse) return except Exception: self.sendReply( '451 Error in processing (connection to remote smtp server failed)', event.SendSMTPMailResponse) return try: self.server.ehlo_or_helo_if_needed() except smtplib.SMTPHeloError as excpt: self.sendReply('451 Error in processing (%s)' % excpt.resp, event.SendSMTPMailResponse) return try: (code, reply) = self.server.mail(address) # TODO: process reply code if 250 != code: self.sendReply((code, reply), event.SendSMTPMailResponse) return except Exception: self.sendReply( '451 Error in processing (connection to remote smtp server failed)', event.SendSMTPMailResponse) return # end validation self.transaction = SMTPTransaction(address) print('sender:', self.transaction.mailfrom, file=debug.stream()) self.sendOK(event.SendSMTPMailResponse)
def connect_if_needed(self, host, port): print("connect_if_needed()", file=debug.stream()) if not self.connected: print(" not connected, try connect", file=debug.stream()) try: reply = self.connect(host, port) print(" reply (%s, %s)" % reply, file=debug.stream()) if 220 == reply[0]: print(" connected = True", file=debug.stream()) self.connected = True return reply except Exception: raise else: return (250, 'Already connected')
def initSMTP(self): event.StartSession.notify() self.fqdn = socket.getfqdn() """ TODO: kann peer wirklich unterschiedlich sein zu address? try: self.peer = self.sock.getpeername() except socket.error as err: # a race condition may occur if the other end is closing # before we can get the peername self.close() if err.args[0] != errno.ENOTCONN: raise return print('Peer:', repr(self.peer), file = debug.stream()) """ print('Address:', repr(self.client.address), file=debug.stream()) result = sfsp.plugin.filter.Filter.validateClientAddress(self.client) if result.failNow() or (not self.sfsp.options.defer_errors and result.failDefer()): self.sendReply(result) elif result.failChoose(): # TODO: choose something? pass self.sendReply((220, '%s %s' % (self.fqdn, self.sfsp.version)), event.SendSMTPBanner) self.smtp_state = self.SMTP_COMMAND
def data_terminated(self, line): if self.smtp_state != self.SMTP_DATA or (self.data_state != self.DATA_HEADER and self.data_state != self.DATA_BODY): self.sendReply((451, 'Internal confusion')) self.num_bytes = 0 return if self.num_bytes > self.data_size_limit: self.sendReply((552, 'Error: Too much mail data')) self.num_bytes = 0 return if '.' == line: # end of transaction self.process_message() self.data_state = self.DATA_NONE else: # Remove extraneous carriage returns and de-transparency according # to RFC 821, Section 4.5.2. if line and '.' == line[0]: line = line[1:] if self.DATA_HEADER == self.data_state: if '' == line: print("end of headers", file = debug.stream()) self.data_state = self.DATA_BODY else: self.transaction.appendHeaderLine(line) else: self.transaction.appendBodyLine(line)
def data_terminated(self, line): if self.smtp_state != self.SMTP_DATA or ( self.data_state != self.DATA_HEADER and self.data_state != self.DATA_BODY): self.sendReply((451, 'Internal confusion')) self.num_bytes = 0 return if self.num_bytes > self.data_size_limit: self.sendReply((552, 'Error: Too much mail data')) self.num_bytes = 0 return if '.' == line: # end of transaction self.process_message() self.data_state = self.DATA_NONE else: # Remove extraneous carriage returns and de-transparency according # to RFC 821, Section 4.5.2. if line and '.' == line[0]: line = line[1:] if self.DATA_HEADER == self.data_state: if '' == line: print("end of headers", file=debug.stream()) self.data_state = self.DATA_BODY else: self.transaction.appendHeaderLine(line) else: self.transaction.appendBodyLine(line)
def initSMTP(self): event.StartSession.notify() self.fqdn = socket.getfqdn() """ TODO: kann peer wirklich unterschiedlich sein zu address? try: self.peer = self.sock.getpeername() except socket.error as err: # a race condition may occur if the other end is closing # before we can get the peername self.close() if err.args[0] != errno.ENOTCONN: raise return print('Peer:', repr(self.peer), file = debug.stream()) """ print('Address:', repr(self.client.address), file = debug.stream()) result = sfsp.plugin.filter.Filter.validateClientAddress(self.client) if result.failNow() or (not self.sfsp.options.defer_errors and result.failDefer()): self.sendReply(result) elif result.failChoose(): # TODO: choose something? pass self.sendReply((220, '%s %s' % (self.fqdn, self.sfsp.version)), event.SendSMTPBanner) self.smtp_state = self.SMTP_COMMAND
def found_terminator(self): line = EMPTYSTRING.join(self.received_lines) print('Data:', repr(line), file = debug.stream()) self.received_lines = [] if self.smtp_state == self.SMTP_COMMAND: self.command_terminated(line) else: self.data_terminated(line)
def found_terminator(self): line = EMPTYSTRING.join(self.received_lines) print('Data:', repr(line), file=debug.stream()) self.received_lines = [] if self.smtp_state == self.SMTP_COMMAND: self.command_terminated(line) else: self.data_terminated(line)
def smtp_MAIL(self, arg): event.ReceivedSMTPMail.notify() print('===> MAIL', arg, file = debug.stream()) # protocol validation (rfc8321) if not self.seen_greeting: self.sendReply('503 Error: MAIL before (EH|HE)LO', event.SendSMTPMailResponse) return if self.transaction: self.sendReply('503 Error: nested MAIL command', event.SendSMTPMailResponse) return address = self.__getaddr('FROM:', arg) if arg else None if not address: self.sendReply('501 Syntax: MAIL FROM:<address>', event.SendSMTPMailResponse) return # target smtp validation try: (code, reply) = self.server.connect_if_needed(self.sfsp.options.remoteaddress, self.sfsp.options.remoteport) if 220 != code and 250 != code: # TODO: maybe only forward 421? self.sendReply((code, reply), event.SendSMTPMailResponse) return except Exception: self.sendReply('451 Error in processing (connection to remote smtp server failed)', event.SendSMTPMailResponse) return try: self.server.ehlo_or_helo_if_needed() except smtplib.SMTPHeloError as excpt: self.sendReply('451 Error in processing (%s)' % excpt.resp, event.SendSMTPMailResponse) return try: (code, reply) = self.server.mail(address) # TODO: process reply code if 250 != code: self.sendReply((code, reply), event.SendSMTPMailResponse) return except Exception: self.sendReply('451 Error in processing (connection to remote smtp server failed)', event.SendSMTPMailResponse) return # end validation self.transaction = SMTPTransaction(address) print('sender:', self.transaction.mailfrom, file = debug.stream()) self.sendOK(event.SendSMTPMailResponse)
def __getaddr(self, keyword, arg): address = None keylen = len(keyword) print(arg[:keylen].upper(), keyword, file=debug.stream()) if arg[:keylen].upper() == keyword: address = arg[keylen:].strip() if not address: pass # TODO: Advanced address parsing? elif address[0] == '<' and address[-1] == '>' and address != '<>': # Addresses can be in the form <*****@*****.**> but watch out # for null address, e.g. <> address = address[1:-1] return address
def __getaddr(self, keyword, arg): address = None keylen = len(keyword) print(arg[:keylen].upper(), keyword, file = debug.stream()) if arg[:keylen].upper() == keyword: address = arg[keylen:].strip() if not address: pass # TODO: Advanced address parsing? elif address[0] == '<' and address[-1] == '>' and address != '<>': # Addresses can be in the form <*****@*****.**> but watch out # for null address, e.g. <> address = address[1:-1] return address
def validateRecipient(self, evt, session, address): triplet = (session.client.address[0], session.transaction.mailfrom, address) if triplet in Greylisting.whitetriplets: print("found triplet", triplet, "in whitelist, accepting", file=debug.stream()) return self.passed() elif not triplet in Greylisting.greytriplets: print("unknwon triplet", triplet, ", greylisting", file=debug.stream()) Greylisting.greytriplets[triplet] = time.time() return self.greylisted() elif Greylisting.greytriplets[triplet] > time.time() - 15: print("triplet", triplet, "still greylisted, try again later", file=debug.stream()) # TODO: restart waiting? # Greylisting.greytriplets[triplet] = time.time() return self.greylisted() elif Greylisting.greytriplets[triplet] < time.time( ) - 60 * 60 * 24 * 3: # older than 3 days print("triplet", triplet, "greylisted but not seen for very long, try again later", file=debug.stream()) Greylisting.greytriplets[triplet] = time.time() return self.greylisted() else: print("triplet", triplet, "known, moving to whitelist, accepting") Greylisting.greytriplets.pop(triplet) Greylisting.whitetriplets[triplet] = time.time() return self.passed()
def smtp_RCPT(self, arg): event.ReceivedSMTPRcpt.notify() print('===> RCPT', arg, file=debug.stream()) # protocol validation (rfc8321) if not self.transaction: self.sendReply('503 Error: need MAIL command', event.ReceivedSMTPRcpt) return address = self.__getaddr('TO:', arg) if arg else None if not address: self.sendReply('501 Syntax: RCPT TO: <address>', event.ReceivedSMTPRcpt) return # target smtp validation try: (code, reply) = self.server.rcpt(address) if 250 != code: # TODO: maybe examine reply? self.sendReply((code, reply), event.ReceivedSMTPRcpt) return except Exception: self.sendReply('451 Error in processing', event.ReceivedSMTPRcpt) return # end validation result = sfsp.plugin.filter.Filter.validateRecipient(address) if 250 != result.mainresult.smtp_error: self.sendReply( (result.mainresult.smtp_error, result.mainresult.message), event.ReceivedSMTPRcpt) return # end filtering self.transaction.addRecipient(address) print('recips:', self.transaction.recipients, file=debug.stream()) self.sendOK(event.ReceivedSMTPRcpt)
def __init__(self, options, version): self.options = options self.version = version asyncore.dispatcher.__init__(self) try: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) # try to re-use a server port if possible self.set_reuse_addr() self.bind((self.options.localaddress, self.options.localport)) self.listen(5) except: self.close() raise else: print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (self.__class__.__name__, time.ctime(time.time()), (self.options.localaddress, self.options.localport), (self.options.remoteaddress, self.options.remoteport)), file=debug.stream())
def reset_if_needed(self): print("reset_if_needed()", file=debug.stream()) if self.connected: print(" connected, try reset", file=debug.stream()) self.rset()
def handle_accepted(self, socket, addr): print('Incoming connection from %s' % repr(addr), file = debug.stream()) channel = SMTPSession(self, socket, addr) channel.initSMTP()
def SendSMPTBanner(self, event, session): delay = int(Delay.delay - (time.time() - self.start_time)) if 0 < delay: print("Delaying for %d seconds" % delay, file=debug.stream()) time.sleep(delay)
def SendSMPTBanner(self, event, session): delay = int(Delay.delay - (time.time() - self.start_time)) if 0 < delay: print("Delaying for %d seconds" % delay, file = debug.stream()) time.sleep(delay)
def __init__(self, options, version): self.options = options self.version = version asyncore.dispatcher.__init__(self) try: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) # try to re-use a server port if possible self.set_reuse_addr() self.bind((self.options.localaddress, self.options.localport)) self.listen(5) except: self.close() raise else: print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % ( self.__class__.__name__, time.ctime(time.time()), (self.options.localaddress, self.options.localport), (self.options.remoteaddress, self.options.remoteport)), file = debug.stream())
def registerPlugin(cls, pluginClass, pluginScope): print('loading plugin', pluginClass.__name__, 'from', pluginClass.__module__, file = debug.stream()) if event.Scope.GLOBAL == pluginScope: plugin = pluginClass() else: plugin = pluginClass for key, func in pluginClass.__dict__.items(): if hasattr(func, 'eventListener'): for (evt, priority) in getattr(func, 'eventListener'): evt.register(plugin, key, priority, pluginScope) cls.loadedPlugins.add(pluginClass)
def handle_accepted(self, socket, addr): print('Incoming connection from %s' % repr(addr), file=debug.stream()) channel = SMTPSession(self, socket, addr) channel.initSMTP()