#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
#  Check SPF results and provide recommended action back to Postfix.
#
#  Tumgreyspf source
#  Copyright © 2004-2005, Sean Reifschneider, tummy.com, ltd.
#  <jafo@tummy.com>
#
#  pypolicyd-spf
#  Copyright © 2007-16, Scott Kitterman <scott@kitterman.com>
'''
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
'''

def main():
    __version__ = "2.9.3"

    import syslog
    import os
    import socket
    import sys
    import re

    import spf_engine
    import spf_engine.policydspfsupp as policydspfsupp
    # import policydspfuser - Only imported if per-user settings are activated for
    # efficiency.

    if int(sys.version_info.major) < 3 or (int(sys.version_info.major) == 3 and \
            int(sys.version_info.minor) < 3):
        raise ImportError("Python 3.3 or later is required")

    syslog.openlog(os.path.basename(sys.argv[0]), syslog.LOG_PID, syslog.LOG_MAIL)
    policydspfsupp._setExceptHook()

    #  load config file  {{{1
    #  Default location:
    configFile = '/etc/python-policyd-spf/policyd-spf.conf'
    if len(sys.argv) > 1:
        if sys.argv[1] in ( '-?', '--help', '-h' ):
            print('usage: policyd-spf [<configfilename>]')
            sys.exit(1)
        configFile = sys.argv[1]

    configGlobal = policydspfsupp._processConfigFile(filename = configFile)

    #  loop reading data  {{{1
    debugLevel = configGlobal.get('debugLevel', 1)
    if debugLevel >= 3: syslog.syslog('Starting')
    instance_dict = {'0':'init',}
    instance_dict.clear()
    data = {}
    lineRx = re.compile(r'^\s*([^=\s]+)\s*=(.*)$')
    while 1:
        # Python readline assumes ascii here, but sometimes it's not
        lineraw = sys.stdin.buffer.readline()
        line = lineraw.decode('UTF-8',errors='replace')
        if not line: break
        line = line.rstrip()
        if debugLevel >= 4: syslog.syslog('Read line: "%s"' % line)

        #  end of entry  {{{2
        if not line:
            peruser = False
            peruserconfigData = []
            rejectAction = ''
            deferAction = ''
            if debugLevel >= 4: syslog.syslog('Found the end of entry')
            configData = dict(list(configGlobal.items()))
            if configData.get('defaultSeedOnly') is not None:
                syslog.syslog(syslog.LOG_ERR,'WARNING: Deprecated Config Option defaultSeedOnly in use in: {0}'.format(configFile ))

            if configData.get('Mock'):
                sys.stdout.write('action=dunno mock header field that should be ignored\n\n')
                continue
            if not data.get('recipient'):
                data['recipient'] = 'none'
            if configData.get('Per_User'):
                import spf_engine.policydspfuser
                peruserconfigData, peruser = spf_engine.policydspfuser._datacheck(configData, data.get('recipient'))
                if debugLevel >= 2 and peruser: syslog.syslog('Per user configuration data in use for: %s' \
                    % str(data.get('recipient')))
            if configData.get('Hide_Receiver') != 'No':
                data['recipient'] = '<UNKNOWN>'
            if debugLevel >= 3: syslog.syslog('Config: %s' % str(configData))
            #  run the checkers  {{{3
            checkerValue = None
            checkerReason = None
            checkerValue, checkerReason, instance_dict, iserror = \
                    spf_engine._spfcheck(data, instance_dict, configData,
                    peruser, peruserconfigData)

            if not peruser:
                TestOnly = configData.get('defaultSeedOnly')
                TestOnly = configData.get('TestOnly')
            else:
                TestOnly = peruserconfigData.get('defaultSeedOnly')
                TestOnly = peruserconfigData.get('TestOnly')
            if TestOnly == 0 and checkerValue != 'prepend':
                checkerValue = None
                checkerReason = None

            if configData.get('SPF_Enhanced_Status_Codes') == 'No':
                rejectAction = '550'
                deferAction = 'defer_if_permit'
            else:
                if iserror:
                    rejectAction = '550 5.7.24'
                    deferAction = 'defer_if_permit 4.7.24'
                else:
                    rejectAction = '550 5.7.23'
                    deferAction = 'defer_if_permit 4.7.24'

            #  handle results  {{{3
            if debugLevel >= 3: syslog.syslog('Action: {0}: Text: {1} Reject action: {2}'.format(checkerValue, checkerReason, rejectAction))
            if checkerValue == 'reject':
                if debugLevel >= 1: syslog.syslog('{0} {1}'.format(rejectAction, checkerReason))
                sys.stdout.write('action={0} {1}\n\n'.format(rejectAction, checkerReason))

            elif checkerValue == 'prepend':
                if configData.get('Prospective'):
                    sys.stdout.write('action=dunno\n\n')
                else:
                    if configData.get('Header_Type') != 'None':
                        if debugLevel >= 1: syslog.syslog('prepend {0}'.format(checkerReason))
                        try:
                            sys.stdout.write('action=prepend %s\n\n' % checkerReason)
                        except UnicodeEncodeError:
                            sys.stdout.write('action=prepend %s\n\n' % str(checkerReason.encode("UTF-8"))[1:].strip("'"))
                    else:
                        if debugLevel >= 1: syslog.syslog('Header field not prepended: {0}'.format(checkerReason))
                        sys.stdout.write('action=dunno\n\n')

            elif checkerValue == 'defer':
                if debugLevel >= 1: syslog.syslog('{0} {1}'.format(deferAction, checkerReason))
                try:
                    sys.stdout.write('action={0} {1}\n\n'.format(deferAction, checkerReason))
                except UnicodeEncodeError:
                    sys.stdout.write('action={0} {1}\n\n'.format(deferAction, str(checkerReason.encode("UTF-8"))[1:].strip("'")))

            elif checkerValue == 'warn':
                try:
                   sys.stdout.write('action=warn %s\n\n' % checkerReason)
                except UnicodeEncodeError:
                    sys.stdout.write('action=warn %s\n\n' % str(checkerReason.encode("UTF-8"))[1:].strip("'"))

            elif checkerValue == 'result_only':
                if debugLevel >= 1: syslog.syslog('result_only {0}'.format(checkerReason))
                try:
                    sys.stdout.write('action=%s\n\n' % checkerReason)
                except UnicodeEncodeError:
                    sys.stdout.write('action=%s\n\n' % str(checkerReason.encode("UTF-8"))[1:].strip("'"))

            else:
                sys.stdout.write('action=dunno\n\n')

            #  end of record  {{{3
            sys.stdout.flush()
            data = {}
            continue

        #  parse line  {{{2
        m = lineRx.match(line)
        if not m: 
            if debugLevel >= 0: syslog.syslog('ERROR: Could not match line "%s"' % line)
            continue

        #  save the string  {{{2
        key = m.group(1)
        value = m.group(2)
        if key not in [ 'protocol_state', 'protocol_name', 'queue_id' ]:
            value = value.lower()
        data[key] = value

    if debugLevel >= 3: syslog.syslog('Normal exit')
