def test_check_helo_domain(self): for host in self.dynamic_hosts + self.static_hosts: params = { 'client_name': host, 'helo_name': 'mail.%s' % host, } self.assertEquals(bleyhelpers.check_helo(params), 1)
def test_check_helo_bad(self): for ip in self.ips: params = { 'client_name': '%s.dyn.example.com' % ip[1], 'client_address': ipaddr.IPAddress(ip[0]).exploded, 'helo_name': 'windowsxp.local', } self.assertEquals(bleyhelpers.check_helo(params), 2)
def test_check_helo_ip(self): for ip in self.ips: params = { 'client_name': '%s.dyn.example.com' % ip[1], 'client_address': ipaddress.ip_address(six.u(ip[0])).exploded, 'helo_name': '[%s]' % ip[0], } self.assertEquals(bleyhelpers.check_helo(params), 1)
def check_policy(self): '''Check the incoming mail based on our policy and tell Postfix about our decision. The policy works as follows: 1. Accept if recipient=(postmaster|abuse) 2. Check local DB for an existing entry 3. When not found, check 1. DNSWLs (accept if found) 2. DNSBLs (reject if found) 3. HELO/dyn_host/sender_eq_recipient (reject if over threshold) 4. SPF (reject if over threshold) 5. Accept if not yet rejected 4. When found 1. Whitelisted: accept 2. Greylisted and waited: accept 3. Greylisted and not waited: reject @type postfix_params: dict @param postfix_params: parameters we got from Postfix ''' if not self.db: self.db = self.factory.settings.db try: self.dbc = self.db.cursor() except: self.safe_reconnect() check_results = { 'DNSWL': 0, 'DNSBL': 0, 'HELO': 0, 'DYN': 0, 'DB': -1, 'SPF': 0, 'S_EQ_R': 0, 'WHITELISTED': 0, 'CACHE': 0 } action = 'DUNNO' self.params['now'] = datetime.datetime.now() postfix_params = self.params # Strip everything after a + in the localpart, # usefull for mailinglists etc if postfix_params['sender'].find('+') != -1: postfix_params['sender'] = postfix_params[ 'sender'][:postfix_params['sender']. find('+')] + postfix_params['sender'][ postfix_params['sender'].find('@'):] if postfix_params['recipient'].find('+') != -1: postfix_params['recipient'] = postfix_params[ 'recipient'][:postfix_params['recipient']. find('+')] + postfix_params['recipient'][ postfix_params['recipient'].find('@'):] if postfix_params['client_address'] in self.factory.bad_cache.keys(): delta = datetime.datetime.now() - self.factory.bad_cache[ postfix_params['client_address']] if delta < datetime.timedelta(0, self.factory.settings.cache_valid, 0): action = 'DEFER_IF_PERMIT %s (cached result)' % self.factory.settings.reject_msg check_results['CACHE'] = 1 if self.factory.settings.verbose: logger.info( 'decided CACHED action=%s, checks: %s, postfix: %s' % (action, check_results, postfix_params)) else: logger.info('decided CACHED action=%s, from=%s, to=%s' % (action, postfix_params['sender'], postfix_params['recipient'])) self.send_action(action) self.factory.log_action(postfix_params, action, check_results) return else: del self.factory.bad_cache[postfix_params['client_address']] if postfix_params['client_address'] in self.factory.good_cache.keys(): delta = datetime.datetime.now() - self.factory.good_cache[ postfix_params['client_address']] if delta < datetime.timedelta(0, self.factory.settings.cache_valid, 0): action = 'DUNNO' check_results['CACHE'] = 1 if self.factory.settings.verbose: logger.info( 'decided CACHED action=%s, checks: %s, postfix: %s' % (action, check_results, postfix_params)) else: logger.info('decided CACHED action=%s, from=%s, to=%s' % (action, postfix_params['sender'], postfix_params['recipient'])) self.send_action(action) self.factory.log_action(postfix_params, action, check_results) return else: del self.factory.good_cache[postfix_params['client_address']] status = self.check_local_db(postfix_params) # -1 : not found # 0 : regular host, not in black, not in white, let it go # 1 : regular host, but in white, let it go, dont check EHLO # 2 : regular host, but in black, lets grey for now if self.check_whitelist(postfix_params['recipient'].lower(), self.factory.settings.whitelist_recipients): action = 'DUNNO' check_results['WHITELISTED'] = 1 elif self.check_whitelist(postfix_params['client_name'].lower(), self.factory.settings.whitelist_clients): action = 'DUNNO' check_results['WHITELISTED'] = 1 elif self.check_whitelist_ip( postfix_params['client_address'].lower(), self.factory.settings.whitelist_clients_ip): action = 'DUNNO' check_results['WHITELISTED'] = 1 elif status == -1: # not found in local db... check_results['DNSWL'] = yield self.check_dnswls( postfix_params['client_address'], self.factory.settings.dnswl_threshold) if check_results['DNSWL'] >= self.factory.settings.dnswl_threshold: new_status = 1 else: check_results['DNSBL'] = yield self.check_dnsbls( postfix_params['client_address'], self.factory.settings.dnsbl_threshold) check_results['HELO'] = bleyhelpers.check_helo(postfix_params) check_results['DYN'] = bleyhelpers.check_dyn_host( postfix_params['client_name']) # check_sender_eq_recipient: if postfix_params['sender'] == postfix_params['recipient']: check_results['S_EQ_R'] = 1 if self.factory.settings.use_spf and check_results[ 'DNSBL'] < self.factory.settings.dnsbl_threshold and check_results[ 'HELO'] + check_results['DYN'] + check_results[ 'S_EQ_R'] < self.factory.settings.rfc_threshold: check_results['SPF'] = bleyhelpers.check_spf( postfix_params, self.factory.settings.use_spf_guess) else: check_results['SPF'] = 0 if check_results[ 'DNSBL'] >= self.factory.settings.dnsbl_threshold or check_results[ 'HELO'] + check_results['DYN'] + check_results[ 'SPF'] + check_results[ 'S_EQ_R'] >= self.factory.settings.rfc_threshold: new_status = 2 action = 'DEFER_IF_PERMIT %s' % self.factory.settings.reject_msg self.factory.bad_cache[postfix_params[ 'client_address']] = datetime.datetime.now() else: new_status = 0 self.factory.good_cache[postfix_params[ 'client_address']] = datetime.datetime.now() query = "INSERT INTO bley_status (ip, status, last_action, sender, recipient) VALUES(%(client_address)s, %(new_status)s, %(now)s, %(sender)s, %(recipient)s)" postfix_params['new_status'] = new_status try: self.safe_execute(query, postfix_params) except: # the other thread already commited while we checked, ignore pass elif status[0] >= 2: # found to be greyed check_results['DB'] = status[0] delta = datetime.datetime.now() - status[1] if delta > self.factory.settings.greylist_period + status[ 2] * self.factory.settings.greylist_penalty or delta > self.factory.settings.greylist_max: action = 'DUNNO' query = "UPDATE bley_status SET status=0, last_action=%(now)s WHERE ip=%(client_address)s AND sender=%(sender)s AND recipient=%(recipient)s" self.factory.good_cache[postfix_params[ 'client_address']] = datetime.datetime.now() else: action = 'DEFER_IF_PERMIT %s' % self.factory.settings.reject_msg query = "UPDATE bley_status SET fail_count=fail_count+1 WHERE ip=%(client_address)s AND sender=%(sender)s AND recipient=%(recipient)s" self.factory.bad_cache[postfix_params[ 'client_address']] = datetime.datetime.now() self.safe_execute(query, postfix_params) else: # found to be clean check_results['DB'] = status[0] action = 'DUNNO' query = "UPDATE bley_status SET last_action=%(now)s WHERE ip=%(client_address)s AND sender=%(sender)s AND recipient=%(recipient)s" self.safe_execute(query, postfix_params) self.factory.good_cache[ postfix_params['client_address']] = datetime.datetime.now() if self.factory.settings.verbose: logger.info('decided action=%s, checks: %s, postfix: %s' % (action, check_results, postfix_params)) else: logger.info('decided action=%s, from=%s, to=%s' % (action, postfix_params['sender'], postfix_params['recipient'])) self.factory.log_action(postfix_params, action, check_results) self.send_action(action)
def check_policy(self): '''Check the incoming mail based on our policy and tell Postfix about our decision. The policy works as follows: 1. Accept if recipient=(postmaster|abuse) 2. Check local DB for an existing entry 3. When not found, check 1. DNSWLs (accept if found) 2. DNSBLs (reject if found) 3. HELO/dyn_host/sender_eq_recipient (reject if over threshold) 4. SPF (reject if over threshold) 5. Accept if not yet rejected 4. When found 1. Whitelisted: accept 2. Greylisted and waited: accept 3. Greylisted and not waited: reject @type postfix_params: dict @param postfix_params: parameters we got from Postfix ''' if not self.db: self.db = self.factory.settings.db try: self.dbc = self.db.cursor() except: self.safe_reconnect() check_results = {'DNSWL': 0, 'DNSBL': 0, 'HELO': 0, 'DYN': 0, 'DB': -1, 'SPF': 0, 'S_EQ_R': 0, 'WHITELISTED': 0, 'CACHE': 0} action = 'DUNNO' self.params['now'] = datetime.datetime.now() postfix_params = self.params # Strip everything after a + in the localpart, # usefull for mailinglists etc if postfix_params['sender'].find('+') != -1: postfix_params['sender'] = postfix_params['sender'][:postfix_params['sender'].find('+')] + postfix_params['sender'][postfix_params['sender'].find('@'):] if postfix_params['recipient'].find('+') != -1: postfix_params['recipient'] = postfix_params['recipient'][:postfix_params['recipient'].find('+')] + postfix_params['recipient'][postfix_params['recipient'].find('@'):] if postfix_params['client_address'] in self.factory.bad_cache.keys(): delta = datetime.datetime.now() - self.factory.bad_cache[postfix_params['client_address']] if delta < datetime.timedelta(0, self.factory.settings.cache_valid, 0): action = 'DEFER_IF_PERMIT %s (cached result)' % self.factory.settings.reject_msg check_results['CACHE'] = 1 if self.factory.settings.verbose: logger.info('decided CACHED action=%s, checks: %s, postfix: %s' % (action, check_results, postfix_params)) else: logger.info('decided CACHED action=%s, from=%s, to=%s' % (action, postfix_params['sender'], postfix_params['recipient'])) self.send_action(action) self.factory.log_action(postfix_params, action, check_results) return else: del self.factory.bad_cache[postfix_params['client_address']] if postfix_params['client_address'] in self.factory.good_cache.keys(): delta = datetime.datetime.now() - self.factory.good_cache[postfix_params['client_address']] if delta < datetime.timedelta(0, self.factory.settings.cache_valid, 0): action = 'DUNNO' check_results['CACHE'] = 1 if self.factory.settings.verbose: logger.info('decided CACHED action=%s, checks: %s, postfix: %s' % (action, check_results, postfix_params)) else: logger.info('decided CACHED action=%s, from=%s, to=%s' % (action, postfix_params['sender'], postfix_params['recipient'])) self.send_action(action) self.factory.log_action(postfix_params, action, check_results) return else: del self.factory.good_cache[postfix_params['client_address']] status = self.check_local_db(postfix_params) # -1 : not found # 0 : regular host, not in black, not in white, let it go # 1 : regular host, but in white, let it go, dont check EHLO # 2 : regular host, but in black, lets grey for now if self.check_whitelist(postfix_params['recipient'].lower(), self.factory.settings.whitelist_recipients): action = 'DUNNO' check_results['WHITELISTED'] = 1 elif self.check_whitelist(postfix_params['client_name'].lower(), self.factory.settings.whitelist_clients): action = 'DUNNO' check_results['WHITELISTED'] = 1 elif self.check_whitelist_ip(postfix_params['client_address'].lower(), self.factory.settings.whitelist_clients_ip): action = 'DUNNO' check_results['WHITELISTED'] = 1 elif status == -1: # not found in local db... check_results['DNSWL'] = yield self.check_dnswls(postfix_params['client_address'], self.factory.settings.dnswl_threshold) if check_results['DNSWL'] >= self.factory.settings.dnswl_threshold: new_status = 1 else: check_results['DNSBL'] = yield self.check_dnsbls(postfix_params['client_address'], self.factory.settings.dnsbl_threshold) check_results['HELO'] = bleyhelpers.check_helo(postfix_params) check_results['DYN'] = bleyhelpers.check_dyn_host(postfix_params['client_name']) # check_sender_eq_recipient: if postfix_params['sender'] == postfix_params['recipient']: check_results['S_EQ_R'] = 1 if self.factory.settings.use_spf and check_results['DNSBL'] < self.factory.settings.dnsbl_threshold and check_results['HELO'] + check_results['DYN'] + check_results['S_EQ_R'] < self.factory.settings.rfc_threshold: check_results['SPF'] = bleyhelpers.check_spf(postfix_params, self.factory.settings.use_spf_guess) else: check_results['SPF'] = 0 if check_results['DNSBL'] >= self.factory.settings.dnsbl_threshold or check_results['HELO'] + check_results['DYN'] + check_results['SPF'] + check_results['S_EQ_R'] >= self.factory.settings.rfc_threshold: new_status = 2 action = 'DEFER_IF_PERMIT %s' % self.factory.settings.reject_msg self.factory.bad_cache[postfix_params['client_address']] = datetime.datetime.now() else: new_status = 0 self.factory.good_cache[postfix_params['client_address']] = datetime.datetime.now() query = "INSERT INTO bley_status (ip, status, last_action, sender, recipient) VALUES(%(client_address)s, %(new_status)s, %(now)s, %(sender)s, %(recipient)s)" postfix_params['new_status'] = new_status try: self.safe_execute(query, postfix_params) except: # the other thread already commited while we checked, ignore pass elif status[0] >= 2: # found to be greyed check_results['DB'] = status[0] delta = datetime.datetime.now() - status[1] if delta > self.factory.settings.greylist_period + status[2] * self.factory.settings.greylist_penalty or delta > self.factory.settings.greylist_max: action = 'DUNNO' query = "UPDATE bley_status SET status=0, last_action=%(now)s WHERE ip=%(client_address)s AND sender=%(sender)s AND recipient=%(recipient)s" self.factory.good_cache[postfix_params['client_address']] = datetime.datetime.now() else: action = 'DEFER_IF_PERMIT %s' % self.factory.settings.reject_msg query = "UPDATE bley_status SET fail_count=fail_count+1 WHERE ip=%(client_address)s AND sender=%(sender)s AND recipient=%(recipient)s" self.factory.bad_cache[postfix_params['client_address']] = datetime.datetime.now() self.safe_execute(query, postfix_params) else: # found to be clean check_results['DB'] = status[0] action = 'DUNNO' query = "UPDATE bley_status SET last_action=%(now)s WHERE ip=%(client_address)s AND sender=%(sender)s AND recipient=%(recipient)s" self.safe_execute(query, postfix_params) self.factory.good_cache[postfix_params['client_address']] = datetime.datetime.now() if self.factory.settings.verbose: logger.info('decided action=%s, checks: %s, postfix: %s' % (action, check_results, postfix_params)) else: logger.info('decided action=%s, from=%s, to=%s' % (action, postfix_params['sender'], postfix_params['recipient'])) self.factory.log_action(postfix_params, action, check_results) self.send_action(action)