#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

#   Copyright 2009-2021 Oli Schacher, Fumail Project
#
# 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.
#


# This tool is used to filter messages in fuglu from the command line

import argparse
from fuglu.scansession import SessionHandler
from fuglu.protocolbase import ProtocolHandler
from fuglu.core import MainController
from fuglu.shared import Suspect
from fuglu.connectors.smtpconnector import buildmsgsource
import sys
import os
import tempfile
import logging
import socket
import configparser


class DummySocket(object):
    def __init__(self):
        pass

    def getsockname(self):
        return '',0


def load_message(filename):
    if filename == '-':
        stream = sys.stdin
    else:
        stream = open(filename, 'r')
    content = stream.read()
    return content


class ConsoleConnector(ProtocolHandler):
    protoname = 'CONSOLE'

    def __init__(self, options):
        self.options = options
        self.socket = DummySocket()
        self.exitcode = 1

    def get_suspect(self, **kwargs):
        (handle, tempfilename) = tempfile.mkstemp(
            prefix='fuglu')

        fd = os.fdopen(handle, 'w+b')
        content=load_message(self.options.input)
        try:
            fd.write(content.encode("utf-8","strict"))
        except Exception as e:
            from inspect import currentframe, getframeinfo
            frameinfo = getframeinfo(currentframe())
            sys.stderr.write("{}:{} {}".format(frameinfo.filename, frameinfo.lineno,str(e)))
            raise e

        fd.close()
        fromaddr = "unknown@example.org"
        if self.options.sender:
            fromaddr = self.options.sender
        toaddr = "unknown@example.org"
        if self.options.recipient:
            toaddr = self.options.recipient
        suspect = Suspect(fromaddr, toaddr, tempfilename)
        suspect.recipients = [toaddr, ]
        return suspect

    def commitback(self, suspect):
        msgcontent = buildmsgsource(suspect)
        print(msgcontent)
        self.exitcode = 0
        sys.stderr.write('DUNNO\n')

    def defer(self, reason):
        self.exitcode = 4
        sys.stderr.write("DEFER\n")

    def discard(self, reason):
        self.exitcode = 2
        sys.stderr.write("DISCARD\n")

    def reject(self, reason):
        self.exitcode = 3
        sys.stderr.write("REJECT\n")


class ConsoleInterface(object):

    def __init__(self, options):
        self.options = options

    def _load_config(self):
        theconfigfile = self.options.config
        dconfdir = '/etc/fuglu/conf.d'
        config = configparser.RawConfigParser()
        if not os.path.exists(theconfigfile):
            print(
                """Configfile (%s) not found. Please create it by renaming the .dist file and modifying it to your needs""" % theconfigfile)
            sys.exit(1)
        with open(theconfigfile) as fp:
            config.read_file(fp)
        # load conf.d
        if theconfigfile == '/etc/fuglu/fuglu.conf' and dconfdir and os.path.isdir(dconfdir):
            filelist = os.listdir(dconfdir)
            configfiles = [dconfdir + '/' + c for c in filelist if c.endswith('.conf')]
            config.read(configfiles)
        return config

    def _boot_controller(self, config):
        controller = MainController(config)
        controller.propagate_core_defaults()
        controller.load_extensions()
        controller.load_plugins()
        return controller

    def direct(self):
        config = self._load_config()
        controller = self._boot_controller(config)
        connector = ConsoleConnector(options)
        appenders = []
        if self.options.appenders:
            appenders = controller.appenders
        session = SessionHandler(connector, config, controller.prependers,
                                 controller.plugins, appenders, self.options.port, {})
        session.handlesession()
        return connector.exitcode

    def netcat(self):
        content = load_message(self.options.input)
        ipvers=socket.AF_INET
        host=self.options.host
        if ':' in host:
            ipvers=socket.AF_INET6
        s = socket.socket(ipvers, socket.SOCK_STREAM)
        s.settimeout(20)
        port = self.options.port
        s.connect((host,port),)
        sockfile=s.makefile('rw')
        banner=sockfile.readline()
        sockfile.write(content)
        sockfile.flush()
        s.shutdown(socket.SHUT_WR)
        reply=sockfile.read()
        retcode,message=reply.split(':',1)
        print(message)
        sys.stderr.write("%s\n"%retcode)
        excode=1
        if retcode=='DUNNO':
            excode=0
        elif retcode=='DEFER':
            excode=4
        elif retcode=='DISCARD':
            excode=2
        elif retcode=='REJECT':
            excode=3
        return excode

def doit(options):
    interface = ConsoleInterface(options)
    if options.direct:
        retcode=interface.direct()
    else:
        retcode=interface.netcat()
    if options.exitcodes:
        sys.exit(retcode)


if __name__=='__main__':
    logging.basicConfig(level=logging.INFO)
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", "--input", default="-", help="message input file. - for stdin")
    parser.add_argument("-s", "--sender", help="envelope sender (only used in direct mode)")
    parser.add_argument("-r", "--recipient", help="envelope recipient (only used in direct mode)")
    parser.add_argument("--host", default="localhost", help="host where fuglu daemon is running")
    parser.add_argument("-p", "--port", type=int, default=20099, help="port on which the netcat connector is listening")
    parser.add_argument("-d", "--direct", action="store_true",
                        help="don't connect to a running fuglu, start your own scanning process")
    parser.add_argument("-a", "--appenders", action="store_true",
                        help="run appenders in direct mode as well. Only used when -d is used")
    parser.add_argument("-e", "--exitcodes", action="store_true",
                        help="exit >1 to indicate a message was DEFERRED or REJECTED")
    parser.add_argument("-c", "--config", default="/etc/fuglu/fuglu.conf", help="use a different config file in direct mode")

    options = parser.parse_args()

    doit(options)
