def restriction(**kwargs): rdns_name = kwargs['smtp_session_data']['reverse_client_name'] client_address = kwargs['smtp_session_data']['client_address'] # Bypass outgoing emails. if kwargs['sasl_username']: logger.debug('Found SASL username, bypass rDNS check for outbound.') return SMTP_ACTIONS['default'] if rdns_name == 'unknown': logger.debug('No reverse dns name, bypass.') return SMTP_ACTIONS['default'] if is_trusted_client(client_address): return SMTP_ACTIONS['default'] _policy_rdns_names = [rdns_name] _splited = rdns_name.split('.') for i in range(len(_splited)): _name = '.' + '.'.join(_splited) _policy_rdns_names.append(_name) _splited.pop(0) logger.debug('All policy rDNS names: %s' % repr(_policy_rdns_names)) conn = kwargs['conn_iredapd'] # Query whitelist sql = """SELECT rdns FROM wblist_rdns WHERE rdns IN %s AND wb='W' LIMIT 1""" % sqlquote(_policy_rdns_names) logger.debug('[SQL] Query whitelisted rDNS names: \n%s' % sql) qr = conn.execute(sql) record = qr.fetchone() if record: rdns = str(record[0]).lower() logger.info("[{}] Reverse client hostname is whitelisted: {}.".format( client_address, rdns)) # better use 'DUNNO' instead of 'OK' return SMTP_ACTIONS['default'] # Query blacklist sql = """SELECT rdns FROM wblist_rdns WHERE rdns IN %s AND wb='B' LIMIT 1""" % sqlquote(_policy_rdns_names) logger.debug('[SQL] Query blacklisted rDNS names: \n%s' % sql) qr = conn.execute(sql) record = qr.fetchone() if record: rdns = str(record[0]).lower() logger.info("[{}] Reverse client hostname is blacklisted: {}".format( client_address, rdns)) return reject_action return SMTP_ACTIONS['default']
def restriction(*args, **kwargs): # Bypass authenticated user. if kwargs['sasl_username']: return SMTP_ACTIONS['default'] # Bypass localhost. if is_trusted_client(kwargs['client_address']): return SMTP_ACTIONS['default'] recipient = kwargs['recipient'] rcpt_domain = kwargs['recipient_domain'] if rcpt_domain == server_hostname: if not (recipient.startswith('srs0=') or recipient.startswith('srs1=')): return SMTP_ACTIONS['reject_not_authorized'] return SMTP_ACTIONS['default']
def restriction(**kwargs): # Bypass outgoing emails. if kwargs['sasl_username']: logger.debug('Found SASL username, bypass senderscore checking.') return SMTP_ACTIONS['default'] client_address = kwargs["client_address"] if not utils.is_ipv4(client_address): logger.debug('Client address is not IPv4, bypass senderscore checking.') return SMTP_ACTIONS["default"] if utils.is_trusted_client(client_address): logger.debug('Client address is trusted, bypass senderscore checking.') return SMTP_ACTIONS['default'] score = 100 cache_the_score = False cache_matched = False conn_iredapd = kwargs['conn_iredapd'] # Check cached score from SQL db to speed it up. # # - Sometimes DNS query might be an issue due to slow reply or temporary # network issue, caching the result will help avoid similar issue. # - SQL query is faster than DNS query, especially when server doesn't run # a local DNS server. # - It's normal that same sender server sends few emails in short period. # - Cached results will be cleaned up automatically by cron job # (tools/cleanup_db.py). sql = """ SELECT score FROM senderscore_cache WHERE client_address=%s LIMIT 1 """ % sqlquote(client_address) qr = conn_iredapd.execute(sql) row = qr.fetchone() if row: try: score = int(row[0]) cache_matched = True except Exception as e: logger.error("[{0}] senderscore -> Error while converting score " "to integer: {1}".format(client_address, e)) else: (o1, o2, o3, o4) = client_address.split(".") lookup_domain = "{0}.{1}.{2}.{3}.score.senderscore.com".format(o4, o3, o2, o1) try: qr = resv.query(lookup_domain, "A") if not qr: return SMTP_ACTIONS["default"] ip = str(qr[0]) score = int(ip.split(".")[-1]) cache_the_score = True except (resolver.NoAnswer): logger.debug("[{0}] senderscore -> NoAnswer".format(client_address)) cache_the_score = True except resolver.NXDOMAIN: logger.debug("[{0}] senderscore -> NXDOMAIN".format(client_address)) cache_the_score = True except (resolver.Timeout): logger.debug("[{0}] senderscore -> Timeout".format(client_address)) except Exception as e: logger.error("[{0}] senderscore -> Error: {1}".format(client_address, e)) if 0 <= score <= 100: if cache_the_score: # Store the DNS query result as cache. sql = """ INSERT INTO senderscore_cache (client_address, score, time) VALUES (%s, %s, %d) """ % (sqlquote(client_address), sqlquote(score), int(time.time())) try: conn_iredapd.execute(sql) except Exception as e: logger.error("[{0}] senderscore -> Error while caching score: {1}".format(client_address, e)) else: logger.error("Invalid sender score: %d (must between 0-100)" % score) return SMTP_ACTIONS['default'] sender_domain = kwargs["sasl_username_domain"] or kwargs["sender_domain"] log_msg = "[{0}] [{1}] senderscore: {2}".format(client_address, sender_domain, score) if cache_matched: log_msg += " (cache matched)" if score <= reject_score: log_msg += " [REJECT (<= {0})]".format(reject_score) logger.info(log_msg) return SMTP_ACTIONS["reject_low_sender_score"] + client_address logger.info(log_msg) return SMTP_ACTIONS["default"]
def restriction(**kwargs): # Bypass outgoing emails. if kwargs['sasl_username']: logger.debug( 'Found SASL username, bypass greylisting for outbound email.') return SMTP_ACTIONS['default'] client_address = kwargs['client_address'] if utils.is_trusted_client(client_address): return SMTP_ACTIONS['default'] sender = kwargs['sender_without_ext'] sender_domain = kwargs['sender_domain'] recipient = kwargs['recipient_without_ext'] recipient_domain = kwargs['recipient_domain'] policy_recipients = utils.get_policy_addresses_from_email(mail=recipient) policy_senders = utils.get_policy_addresses_from_email(mail=sender) policy_senders += [client_address] # If recipient_domain is an alias domain name, we should check the target # domain. conn_vmail = kwargs['conn_vmail'] alias_target_rcpt_domain = get_alias_target_domain( alias_domain=recipient_domain, conn=conn_vmail) if alias_target_rcpt_domain: _addr = recipient.split('@', 1)[0] + '@' + alias_target_rcpt_domain policy_recipients += utils.get_policy_addresses_from_email(mail=_addr) if utils.is_ipv4(client_address): # Add wildcard ip address: xx.xx.xx.*. policy_senders += client_address.rsplit('.', 1)[0] + '.*' # Get object of IP address type _ip_object = ipaddress.ip_address(client_address) conn_iredapd = kwargs['conn_iredapd'] # Check greylisting whitelists if _is_whitelisted(conn=conn_iredapd, senders=policy_senders, recipients=policy_recipients, client_address=client_address, ip_object=_ip_object): return SMTP_ACTIONS['default'] # Check greylisting settings if not _should_be_greylisted_by_setting(conn=conn_iredapd, recipients=policy_recipients, senders=policy_senders, client_address=client_address, ip_object=_ip_object): return SMTP_ACTIONS['default'] # Bypass if sender server is listed in SPF DNS record of sender domain. if settings.GREYLISTING_BYPASS_SPF: if dnsspf.is_allowed_server_in_spf(sender_domain=sender_domain, ip=client_address): logger.info('[{}] Bypass greylisting due to SPF match ({})'.format( client_address, sender_domain)) return SMTP_ACTIONS['default'] if _client_address_passed_in_tracking(conn=conn_iredapd, client_address=client_address): # Update expire time _now = int(time.time()) _new_expire_time = _now + settings.GREYLISTING_AUTH_TRIPLET_EXPIRE * 24 * 60 * 60 _sql = """UPDATE greylisting_tracking SET record_expired=%d WHERE client_address=%s AND passed=1""" % ( _new_expire_time, sqlquote(client_address)) logger.debug('[SQL] Update expire time of passed client: \n%s' % _sql) conn_iredapd.execute(_sql) return SMTP_ACTIONS['default'] # check greylisting tracking. if _should_be_greylisted_by_tracking(conn=conn_iredapd, sender=sender, sender_domain=sender_domain, recipient=recipient, recipient_domain=recipient_domain, client_address=client_address): if settings.GREYLISTING_TRAINING_MODE: logger.debug("Running in greylisting training mode, bypass.") else: return action_greylisting return SMTP_ACTIONS['default']
def restriction(**kwargs): sasl_username = kwargs['sasl_username'] sasl_username_user = sasl_username.split('@', 1)[0] sasl_username_domain = kwargs['sasl_username_domain'] sender = kwargs['sender_without_ext'] sender_name = '' sender_domain = '' if sender: (sender_name, sender_domain) = sender.split('@', 1) # Leave this to plugin `reject_null_sender`. if sasl_username and not sender: return SMTP_ACTIONS['default'] recipient_domain = kwargs['recipient_domain'] client_address = kwargs['client_address'] real_sasl_username = sasl_username real_sender = sender conn = kwargs['conn_vmail'] # Check emails sent from external network. if not sasl_username: logger.debug('Not an authenticated sender (no sasl_username).') # Bypass trusted networks. # NOTE: if sender sent email through SOGo, smtp session may not # have `sasl_username`. if is_trusted_client(client_address): logger.debug('Bypass trusted client.') return SMTP_ACTIONS['default'] if not check_forged_sender: logger.debug('Skip forged sender checks.') return SMTP_ACTIONS['default'] # Bypass allowed forged sender. if sender in allowed_forged_senders or \ sender_domain in allowed_forged_senders or \ sender_name + '@*' in allowed_forged_senders: logger.debug('Bypass allowed forged sender.') return SMTP_ACTIONS['default'] _is_local_sender_domain = False if sender_domain == recipient_domain: logger.debug('Sender domain is same as recipient domain.') _is_local_sender_domain = True else: if is_local_domain(conn=conn, domain=sender_domain, include_backupmx=False): logger.debug( 'Sender domain is hosted locally, smtp authentication is required.' ) _is_local_sender_domain = True else: logger.debug('Sender domain is NOT hosted locally.') if _is_local_sender_domain: if settings.CHECK_SPF_IF_LOGIN_MISMATCH: logger.debug( 'Check whether client is allowed smtp server against DNS SPF record.' ) # Query DNS to get IP addresses/networks listed in SPF # record of sender domain, reject if not match. if dnsspf.is_allowed_server_in_spf(sender_domain=sender_domain, ip=client_address): logger.debug( 'Sender server is listed in DNS SPF record, bypassed.') return SMTP_ACTIONS['default'] else: logger.debug( 'Sender server is NOT listed in DNS SPF record.') logger.debug('Sender is considered as forged, rejecting') return SMTP_ACTIONS['reject_forged_sender'] else: return SMTP_ACTIONS['default'] # Check emails sent by authenticated users. logger.debug('Sender: %s, SASL username: %s' % (sender, sasl_username)) if sender == sasl_username: logger.debug('SKIP: sender == sasl username.') return SMTP_ACTIONS['default'] # # sender != sasl_username # # If no access settings available, reject directly. if not (allowed_senders or is_strict or allow_list_member): logger.debug('No allowed senders in config file.') return action_reject # Check explicitly allowed senders if allowed_senders: logger.debug('Allowed SASL senders: %s' % ', '.join(allowed_senders)) if sasl_username in allowed_senders: logger.debug('Sender SASL username is explicitly allowed.') return SMTP_ACTIONS['default'] elif sasl_username_domain in allowed_senders: logger.debug('Sender domain name is explicitly allowed.') return SMTP_ACTIONS['default'] elif ('@' + sasl_username_domain in allowed_senders) or ('@.' in allowed_senders): # Restrict to send as users under SAME domain if sasl_username_domain == sender_domain: return SMTP_ACTIONS['default'] else: # Note: not reject email here, still need to check other access settings. logger.debug( 'Sender is not allowed to send email as other user (ALLOWED_LOGIN_MISMATCH_SENDERS).' ) # Check whether sender is a member of mlmmj mailing list. _check_mlmmj_ml = False # Check alias domains and user alias addresses if is_strict or allow_list_member: if is_strict: logger.debug( 'Apply strict restriction (ALLOWED_LOGIN_MISMATCH_STRICTLY=True).' ) if allow_list_member: logger.debug( 'Apply list/alias member restriction (ALLOWED_LOGIN_MISMATCH_LIST_MEMBER=True).' ) if settings.backend == 'ldap': filter_user_alias = '(&(objectClass=mailUser)(mail=%s)(shadowAddress=%s))' % ( sasl_username, sender) filter_list_member = '(&(objectClass=mailUser)(|(mail=%s)(shadowAddress=%s))(memberOfGroup=%s))' % ( sasl_username, sasl_username, sender) filter_alias_member = '(&(objectClass=mailAlias)(|(mail=%s)(shadowAddress=%s))(mailForwardingAddress=%s))' % ( sender, sender, sasl_username) if is_strict and (not allow_list_member): # Query mail account directly query_filter = filter_user_alias success_msg = 'Sender is an user alias address.' elif (not is_strict) and allow_list_member: query_filter = '(|' + filter_list_member + filter_alias_member + ')' success_msg = 'Sender (%s) is member of mail list/alias (%s).' % ( sasl_username, sender) else: # (is_strict and allow_list_member) query_filter = '(|' + filter_user_alias + filter_list_member + filter_alias_member + ')' success_msg = 'Sender (%s) is an user alias address or list/alias member (%s).' % ( sasl_username, sender) qr = conn_utils.get_account_ldif(conn=conn, account=sasl_username, query_filter=query_filter, attrs=['dn']) (_dn, _ldif) = qr if _dn: logger.debug(success_msg) return SMTP_ACTIONS['default'] else: logger.debug( 'Sender is neither user alias address nor member of list/alias.' ) # Check mlmmj query_filter = "(&(objectClass=mailList)(enabledService=mlmmj)(accountStatus=active))" qr = conn_utils.get_account_ldif(conn=conn, account=sender, query_filter=query_filter, attrs=['dn']) (_dn, _ldif) = qr if _dn: _check_mlmmj_ml = True elif settings.backend in ['mysql', 'pgsql']: if is_strict: # Get per-user alias addresses sql = """SELECT address FROM forwardings WHERE address=%s AND forwarding=%s AND is_alias=1 LIMIT 1""" % (sqlquote(sender), sqlquote(real_sasl_username)) logger.debug('[SQL] query per-user alias address: \n%s' % sql) qr = conn.execute(sql) sql_record = qr.fetchone() logger.debug('SQL query result: %s' % str(sql_record)) if sql_record: logger.debug( 'Sender %s is an alias address of smtp auth username %s.' % (sender, real_sasl_username)) return SMTP_ACTIONS['default'] else: logger.debug('No per-user alias address found.') # Get alias domains if sender_domain != sasl_username_domain: sql = """SELECT alias_domain FROM alias_domain WHERE alias_domain=%s AND target_domain=%s LIMIT 1""" % (sqlquote(sender_domain), sqlquote(sasl_username_domain)) logger.debug('[SQL] query alias domains: \n%s' % sql) qr = conn.execute(sql) sql_record = qr.fetchone() logger.debug('SQL query result: %s' % str(sql_record)) if not sql_record: logger.debug('No alias domain found.') else: logger.debug( 'Sender domain %s is an alias domain of %s.' % (sender_domain, sasl_username_domain)) real_sasl_username = sasl_username_user + '@' + sasl_username_domain real_sender = sender_name + '@' + sasl_username_domain # sender_domain is one of alias domains if sender_name != sasl_username_user: logger.debug( 'Sender is not an user alias address.') else: logger.debug( 'Sender is an alias address of sasl username.') return SMTP_ACTIONS['default'] if allow_list_member: # Get alias members sql = """SELECT forwarding FROM forwardings WHERE address=%s AND forwarding=%s AND is_list=1 AND active=1 LIMIT 1""" % (sqlquote(real_sender), sqlquote(real_sasl_username)) logger.debug( '[SQL] query members of mail alias account (%s): \n%s' % (real_sender, sql)) qr = conn.execute(sql) sql_record = qr.fetchone() logger.debug('SQL query result: %s' % str(sql_record)) if sql_record: logger.debug( 'SASL username (%s) is a member of mail alias (%s).' % (sasl_username, sender)) return SMTP_ACTIONS['default'] else: logger.debug('No such mail alias account.') # Check subscribeable (mlmmj) mailing list. sql = """SELECT id FROM maillists WHERE address=%s AND active=1 LIMIT 1""" % sqlquote( real_sender) logger.debug('[SQL] query mailing list account (%s): \n%s' % (real_sender, sql)) qr = conn.execute(sql) sql_record = qr.fetchone() logger.debug('SQL query result: %s' % str(sql_record)) if sql_record: _check_mlmmj_ml = True else: logger.debug('No such mailing list account.') if _check_mlmmj_ml: # Perform mlmmjadmin query. api_auth_token = settings.mlmmjadmin_api_auth_token if api_auth_token and settings.mlmmjadmin_api_endpoint: _api_endpoint = '/'.join([ settings.mlmmjadmin_api_endpoint, real_sender, 'has_subscriber', sasl_username ]) api_headers = { settings.MLMMJADMIN_API_AUTH_TOKEN_HEADER_NAME: api_auth_token } logger.debug('mlmmjadmin api endpoint: {0}'.format(_api_endpoint)) logger.debug('mlmmjadmin api headers: {0}'.format(api_headers)) try: r = requests.get(_api_endpoint, headers=api_headers, verify=False) _json = r.json() if _json['_success']: logger.debug( 'SASL username (%s) is a member of mailing list (%s).' % (sasl_username, sender)) return SMTP_ACTIONS['default'] except Exception as e: logger.error( "Error while querying mlmmjadmin api: {0}".format(e)) return action_reject
def restriction(**kwargs): conn = kwargs['conn_iredapd'] conn_vmail = kwargs['conn_vmail'] # Use SASL username as sender. if not available, use sender in 'From:'. sender = kwargs['sasl_username'] or kwargs['sender_without_ext'] sender_domain = kwargs['sasl_username_domain'] or kwargs['sender_domain'] recipient = kwargs['recipient_without_ext'] recipient_domain = kwargs['recipient_domain'] client_address = kwargs['client_address'] smtp_session_data = kwargs['smtp_session_data'] protocol_state = smtp_session_data['protocol_state'] size = smtp_session_data['size'] recipient_count = int(smtp_session_data['recipient_count']) instance_id = smtp_session_data['instance'] if size: size = int(size) else: size = 0 if sender_domain == recipient_domain and settings.THROTTLE_BYPASS_SAME_DOMAIN: logger.debug('Bypassed. Sender domain is same as recipient domain.') return SMTP_ACTIONS['default'] if settings.THROTTLE_BYPASS_MYNETWORKS: if utils.is_trusted_client(client_address): return SMTP_ACTIONS['default'] # If no smtp auth (sasl_username=<empty>), and not sent from trusted # clients, consider it as external sender. is_external_sender = True if kwargs['sasl_username']: logger.debug( 'Found sasl_username, consider this sender as an internal sender.') is_external_sender = False else: # Consider email from localhost as outbound. # SOGo groupware doesn't perform SMTP authentication if it's running # on same host as SMTP server. if client_address in ['127.0.0.1', '::1']: is_external_sender = False # Apply sender throttling to only sasl auth users. logger.debug('Check sender throttling.') action = apply_throttle(conn=conn, conn_vmail=conn_vmail, user=sender, client_address=client_address, protocol_state=protocol_state, size=size, recipient_count=recipient_count, instance_id=instance_id, is_sender_throttling=True, is_external_sender=is_external_sender) if not action.startswith('DUNNO'): return action # Apply recipient throttling to smtp sessions without sasl_username if kwargs['sasl_username'] and settings.THROTTLE_BYPASS_LOCAL_RECIPIENT: # Both sender and recipient are local. logger.debug('Bypass recipient throttling (found sasl_username).') else: logger.debug('Check recipient throttling.') action = apply_throttle(conn=conn, conn_vmail=conn_vmail, user=recipient, client_address=client_address, protocol_state=protocol_state, size=size, recipient_count=recipient_count, instance_id=instance_id, is_sender_throttling=False) if not action.startswith('DUNNO'): return action return SMTP_ACTIONS['default']