Beispiel #1
0
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']
Beispiel #2
0
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']
Beispiel #3
0
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"]
Beispiel #4
0
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']
Beispiel #5
0
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
Beispiel #6
0
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']