def set(self, option, value, section=None): '''Set the given option to the specified value Keyword arguments: section -- Section name override ''' s = section if section else self.default_section try: self.parser.set(s, option, value) except configparser.NoSectionError as e: error('Configuration problem: %s' % (e))
def items(self, section=None): '''Return a list of (name, value) pairs for each option in the given section. Keyword arguments: section -- Section name override ''' s = section if section else self.default_section try: return self.parser.items(s) except configparser.Error as e: error('Configuration problem: %s' % (e))
def __init__(self, default_section='djmd'): ''' Keyword arguments: default_section -- Section name for get() calls ''' self.default_section = default_section cmd_parser = ArgumentParser(description= 'Don\'t Judge Mail - Postfix Policy Daemon') # Options with no counterpart in conf file cmd_parser.add_argument('--conf', dest='conf_file') cmd_parser.add_argument('--version', action='store_true') # Overridable options # Defaults should be provided in configuration file, not here cmd_parser.add_argument('--allow-hosts', metavar='HOST1,HOST2') cmd_parser.add_argument('--debug', action='store_true') cmd_parser.add_argument('--listen', metavar='IP_ADDR:PORT') cmd_parser.add_argument('--pid-file', metavar='/PATH/TO/daemon_name.pid') cmd_parser.add_argument('--plugins', metavar='PLUGIN1,PLUGIN2') cmd_parser.add_argument('--servers', metavar='N') cmd_parser.add_argument('--init-database', action='store_true') self.cmdline = vars(cmd_parser.parse_args()) if 'version' in self.cmdline and self.cmdline['version']: from djm.__version__ import version print('djmd v%s'%(version)) sys.exit(0) try: self.parser = configparser.SafeConfigParser() if 'conf_file' not in self.cmdline: # OK, no conf file. Add default section where the cmdline args # shall be saved (btw, one can't add section named 'default') self.parser.add_section(default_section) else: cf = open(self.cmdline['conf_file']) self.parser.readfp(cf) cf.close() for k, v in self.cmdline.items(): if v: self.parser.set('djmd', k, str(v)) except configparser.Error as e: error('Configuration problem: %s' % (e), sys.stderr) except EnvironmentError: error('Could not open configuration file: %s' % (self.cmdline['conf_file']), sys.stderr)
def __call__(self, conf, db='default'): if not self.is_green: make_green() self.is_green = True db_params = {} for k, v in conf.items('database:%s' % db): db_params[k] = v try: con = psycopg2.connect(**db_params) return con except psycopg2.Error as e: error('Could not connect to the %s database.' % (db,))
def get(self, option, section=None, default=None, mandatory=True): '''Get an option value for the named section Keyword arguments: section -- Section name override mandatory -- Enforce existance of option value default -- Default value for non mandatory options ''' s = section if section else self.default_section try: return self.parser.get(s, option) except configparser.NoOptionError as e: if not mandatory: return default else: error('Configuration problem: %s' % (e)) except configparser.Error as e: error('Configuration problem: %s' % (e))
def __call__(self, request): conf = self.conf db_name = conf.get('database', 'plugin:quota', 'default', mandatory=False) db = connect(conf, db_name) if not db: return PolicyResponse().dunno() try: quota_keys = { 'sasl_username': request['sasl_username'], 'sasl_sender': request['sasl_sender'], 'sender': request['sender'], 'sender_domain': None, 'recipient': request['recipient'], 'recipient_count': request['recipient_count'], 'recipient_domain': request['recipient'].rsplit('@', 1)[1], 'client_address': request['client_address'], 'size': request['size'], } except (KeyError, IndexError): error('Missing policy request attribute') return PolicyResponse().dunno() if '@' in request['sasl_username']: quota_keys['sender_domain'] = request['sasl_username'].rsplit('@', 1)[1] elif '@' in request['sasl_sender']: quota_keys['sender_domain'] = request['sasl_sender'].rsplit('@', 1)[1] else: quota_keys['sender_domain'] = request['sender'].rsplit('@', 1)[1] hd = hosted_domains() sd = quota_keys['sender_domain'] rd = quota_keys['recipient_domain'] if conf.get('ignore_local', 'plugin:quota') == 'true' and \ (sd == rd or ((sd in hd) and (rd in hd))): return PolicyResponse().dunno() c = cursor(db) action = None for item in self.targeted_policies(c, quota_keys): if item['tracking_id'] is None: self.new_quota_tracking_item(db, c, item, quota_keys) resp = self.quota_for(db, c, item, quota_keys) action = resp.action if action == 'reject': return resp # Stop processing if we've passed positive targeted policies if action: return resp # Maybe our user doesn't exist in quota table? for item in self.new_default_policies(c): self.new_quota_tracking_item(db, c, item, quota_keys) # Process default policies (if any) for item in self.default_policies(c, quota_keys): resp = self.quota_for(db, c, item, quota_keys) if resp.action == 'reject': return resp return PolicyResponse().dunno()
def __call__(self, request): conf = self.conf db_name = conf.get('database', 'plugin:greylist', 'default', mandatory=False) db = connect(conf, db_name) resp = PolicyResponse() if not db: return resp.dunno() try: gk = { 'sender': request['sender'], 'sender_domain': request['sender'].rsplit('@', 1)[1], 'recipient': request['recipient'], 'client_address': request['client_address'], } except (KeyError, IndexError): error('Missing policy request attribute') return resp.dunno() # Don't greylist SASL authenticated users if 'sasl_username' in request and '@' in request['sasl_username']: return resp.dunno() c = cursor(db) # Client whitelist c.execute('''SELECT id FROM greylist_sender_wl WHERE sender=%s OR sender=%s''', (gk['sender_domain'], gk['client_address'])) if c.rowcount > 0: resp.prepend('X-Greylist', 'Whitelisted by djmd') return resp.accept() # Auto whitelisted (per domain) c.execute('''SELECT id FROM greylist_sender_awl WHERE sender_domain=%s AND sender_address=%s''', (gk['sender_domain'], gk['client_address'])) if c.rowcount > 0: c.execute('''UPDATE greylist_sender_awl SET last_seen=NOW() WHERE sender_domain=%s AND sender_address=%s''', (gk['sender_domain'], gk['client_address'])) db.commit() resp.prepend('X-Greylist', 'AWL by djmd') return resp.accept() # New connection or second connection (AWL candidate) try: c.execute('''INSERT INTO greylist_tracking(sender, sender_address, recipient) VALUES(%s, %s, %s)''', (gk['sender'], gk['client_address'], gk['recipient'])) db.commit() except IntegrityError as e: db.rollback() c.execute('''SELECT first_seen, (extract(epoch from (NOW() - first_seen)))::integer as elapsed_time FROM greylist_tracking WHERE sender=%s AND sender_address=%s AND recipient=%s''', (gk['sender'], gk['client_address'], gk['recipient'])) gt = c.fetchone() greylist_delay = int(conf.get('greylist_delay', 'plugin:greylist')) if gt[1] > greylist_delay: try: c.execute('''INSERT INTO greylist_sender_awl(sender_domain, sender_address, first_seen, last_seen) VALUES(%s, %s, %s, NOW())''', (gk['sender_domain'], gk['client_address'], gt[0])) db.commit() except IntegrityError as e: # Already whitelisted, move on db.rollback() # AWL resp.prepend('X-Greylist', 'AWL by djmd') return resp.accept() return resp.reject(msg=conf.get('msg', 'plugin:greylist'))