Exemplo n.º 1
0
    def handle_data(self,
                    smtp_session_data,
                    plugins=None,
                    sender_search_attrlist=None,
                    recipient_search_attrlist=None):
        if not plugins:
            return SMTP_ACTIONS['default'] + ' No enabled plugins'

        protocol_state = smtp_session_data['protocol_state'].upper()
        sender = smtp_session_data.get('sender', '')
        recipient = smtp_session_data.get('recipient', '')
        sasl_username = smtp_session_data.get('sasl_username', '')
        client_address = smtp_session_data.get('client_address', '')

        conn_amavisd = None
        if self.conns['conn_amavisd']:
            conn_amavisd = self.conns['conn_amavisd'].connect()

        conn_iredapd = None
        if self.conns['conn_iredapd']:
            conn_iredapd = self.conns['conn_iredapd'].connect()

        plugin_kwargs = {
            'smtp_session_data': smtp_session_data,
            'conn_vmail': self.conn,
            'conn_amavisd': conn_amavisd,
            'conn_iredapd': conn_iredapd,
            'sender': sender,
            'sender_without_ext': smtp_session_data['sender_without_ext'],
            'recipient': recipient,
            'recipient_without_ext': smtp_session_data['recipient_without_ext'],
            'client_address': client_address,
            'sasl_username': sasl_username,
            'sender_domain': smtp_session_data.get('sender_domain', ''),
            'recipient_domain': smtp_session_data.get('recipient_domain', ''),
            'sasl_username_domain': smtp_session_data.get('sasl_username_domain', ''),
            'base_dn': settings.ldap_basedn,
            'sender_dn': None,
            'sender_ldif': None,
            'recipient_dn': None,
            'recipient_ldif': None,
        }

        for plugin in plugins:
            # Get plugin target smtp protocol state
            try:
                target_protocol_state = plugin.SMTP_PROTOCOL_STATE
            except:
                target_protocol_state = ['RCPT']

            if protocol_state not in target_protocol_state:
                logger.debug("Skip plugin: {} (protocol_state != {})".format(plugin.__name__, protocol_state))
                continue

            # Get LDIF data of sender if required
            try:
                require_local_sender = plugin.REQUIRE_LOCAL_SENDER
            except:
                require_local_sender = False

            if require_local_sender and plugin_kwargs['sender_dn'] is None:
                sender_dn, sender_ldif = conn_utils.get_account_ldif(
                    conn=self.conn,
                    account=sasl_username,
                    attrs=sender_search_attrlist,
                )
                plugin_kwargs['sender_dn'] = sender_dn
                plugin_kwargs['sender_ldif'] = sender_ldif

            # Get LDIF data of recipient if required
            try:
                require_local_recipient = plugin.REQUIRE_LOCAL_RECIPIENT
            except:
                require_local_recipient = False

            if require_local_recipient and plugin_kwargs['recipient_dn'] is None:
                recipient_dn, recipient_ldif = conn_utils.get_account_ldif(
                    conn=self.conn,
                    account=recipient,
                    attrs=recipient_search_attrlist,
                )
                plugin_kwargs['recipient_dn'] = recipient_dn
                plugin_kwargs['recipient_ldif'] = recipient_ldif

            # Apply plugins
            action = utils.apply_plugin(plugin, **plugin_kwargs)

            if not action.startswith('DUNNO'):
                return action

        # Close sql connections.
        try:
            if conn_amavisd:
                conn_amavisd.close()

            conn_iredapd.close()
        except:
            pass

        return SMTP_ACTIONS['default']
Exemplo n.º 2
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