'''
policy.py - SMTP policy service for SAGATOR.

(c) 2005-2022 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

'''

from aglib import *
import stats

__all__ = ['smtpd_policy', 'recipient_policy']

class smtpd_policy(ServiceTCPServer):
  '''
  SMTP policy service.
  
  This service can be used as smtpd policy service for postfix.
  
  Usage: smtpd_polixy(scanners, dbc, host, port, max_children=200)
  
  Where: scanners is an array of policy scanners
           (see README.scanners for more info)
         dbc is an database connection
         host is a an ip address to bind
         port is a port to bind
         max_children is a number defining maximal number of childrens
           for this service
  
  Example: smtpd_policy(SCANNERS, db.sqlite(), '127.0.0.1', 29)
  
  Postfix configuration example:
    /etc/postfix/main.cf:
      smtpd_recipient_restrictions=
              ...
              check_policy_service inet:127.0.0.1:29
              ...

  New in version 0.8.0.
  '''
  name = 'smtpd_policy()'
  def __init__(self, scanners, dbc, host, port, max_children=200):
      self.max_children = max_children
      globals.DBC = dbc
      ServiceTCPServer.__init__(
        self, scanners,
        host, port, smtpd_policy_handler
      )

class smtpd_policy_handler(StreamRequestHandler):
  time1 = None
  def handle(self):
      while True:
        self.stats = stats.statistics()
        # generate ID
        self.time2 = time.strftime("%Y%m%d-%H%M%S",
                                   time.localtime(time.time()))
        if self.time2!=self.time1:
          self.time1, self.timec = self.time2, 1
        globals.gen_id(self.time2, self.timec)
        mail.policy_request = {}
        mail.data = b''
        try:
          while True:
            line = self.rfile.readline()
            if not line:
              break
            debug.echo(8, line)
            mail.data += line
            if line==b'\n' or line==b'\r\n':
              break
            try:
              key, value = line.rstrip(b'\r\n').split(b'=', 1)
              mail.policy_request[key] = value
            except ValueError:
              debug.echo(3, "%s: wrong line: '%s'" \
                            % (self.server.name, line.rstrip()))
          if not mail.data:
            break
          debug.echo(4, "%s: %s %s" \
                     % (self.server.name,
                        time.strftime("%c", time.localtime()),
                        tostr_dict(mail.policy_request)))
          self.wfile.write(b'action=%s\n\n'
                           % checkpolicy(self.server.SCANNERS, False))
          self.stats.policy_update()
        except socket.error as err:
          (ec, es) = err.args
          # ignore exception if connection is already down
          if (ec==107 # Transport endpoint is not connected
              or ec==104 # Connection reset by peer
              or ec==32): # Broken pipe
            debug.echo(3, "%s: %s" % (self.server.name, es))
            return
          else:
            debug.traceback(3, "%s: " % self.server.name)
            self.wfile.write(b'action=warn %s\n\n' % tobytes(es))
        except Exception as err:
          debug.traceback(3, "%s: Unknown exception" % self.server.name)
          self.wfile.write(b'action=warn %s\n\n' % tobytes(err))

class recipient_policy(service):
  '''
  Virtual recipient policy.
  
  This policy check is invoked after an "RCPT TO:" smtp command is received.
  You can use an policy scanner combination as scanner. It is useable for
  postfix's before-queue policy filter or an policy filter for milter.
  This service must be defined before service, which want to use it.
  
  Usage: recipient_policy(scanners, dbc)
  
  Where: scanners is an array of policy scanners
           (see README.scanners for more info)
         dbc is an database connection

  Example: recipient_policy(POLICY_SCANNERS, db.sqlite())

  New in version 0.8.0.
  '''
  def __init__(self, scanners, dbc):
      globals.DBC = dbc
      globals.recipient_policy = scanners
  def start(self):
      return []
