#! /usr/bin/python2.6
# -*- coding: utf-8 -*-

# <pwfilter.py  Receive e-mail filters And white list filter.>
# Copyright (C) <2012> <yasuyosi kimura>
#
# 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 3 of the License,
# or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.

### Revision 0.1.1  2012/08/20 08:00:00  Base version.
### Change comment logic & add HostCache, DYNSplit, Whitelist
### Revision 0.1.0  2012/03/03 18:00:00  Base version.
### Change comment logic & add pwmode
### Revision 0.0.2  2012/02/24 16:00:00  Test version.
### Change comment logic parameter
### Revision 0.0.1  2012/02/16 16:00:00  Test version.
### Change DNSCheck
### Revision 0.0.0  2012/01/13 14:00:00  Test version.
### Vre 0.0.0


import sys
import time
import traceback
import Milter
from Milter.dynip import is_dynip as dynip
from Milter.utils import parseaddr, parse_addr

import re

import smtplib
import email
from email.mime.text import MIMEText
from email.header import Header, decode_header
from email.utils import formatdate

import logging
import logging.handlers

## (システムに合わせて修正が必要です) ##
socketname = "inet:1025@localhost"
sockettimeout = 600

ip4re = re.compile(r'^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$|'
                   '^\[[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*\]$')
hostre = re.compile(r'^[a-z0-9][-a-z0-9]*(\.[a-z0-9][-a-z0-9]*)*')
fqdn = re.compile(r'^[a-z0-9][-a-z0-9]*'
                  '(\.[a-z0-9][-a-z0-9]*)*(\.[a-z]{2,10})$')
fqdnjp = re.compile(r'\.[a-z]{2,2}\.jp$|'
                    '([a-z0-9][-a-z0-9]{2,63}\.[a-z]{2,10})$')

##    %(levelno)s         Numeric logging level for the message (DEBUG, INFO,
##                        WARNING, ERROR, CRITICAL)
## (システムに合わせて修正が必要です) ##
log_filename = "/var/log/pwmail/pwfilter.log"
log_level = logging.INFO

my_logger = logging.getLogger("pwfilter")
my_logger.setLevel(log_level)
log_fh = logging.handlers.RotatingFileHandler(log_filename,
                                              maxBytes=1024000,
                                              backupCount=10)
log_fh.setLevel(logging.DEBUG)
log_fm = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
log_fh.setFormatter(log_fm)
my_logger.addHandler(log_fh)

##  (起動時にパラメータで設定する)  ##
## 受信ドメイン名リスト
my_domainlist = ()
## 受信サーバのグローバルIP
my_hnameip = "192.168.200.100"
## ロギング用メールアドレス「全て小文字」
to_pwadmin = "pwadmin@xxxx1.jp"

## (システムに合わせて修正が必要です) ##
## ロギング用メールポート
sendmail_port = 1026
## postfix recipient_delimiter 受信者の拡張アドレスの区切り文字
recipient_delimiter = '+'


###
### dns コピーを修正
import DNS
from DNS import DNSError
MAX_CNAME = 10

## Lookup DNS records by label and RR type.
# The response can include records of other types that the DNS
# server thinks we might need.
# @param name the DNS label to lookup
# @param qtype the name of the DNS RR type to lookup
# @return a list of ((name,type),data) tuples


def DNSLookup(name, qtype):
  try:
    # To be thread safe, we create a fresh DnsRequest with
    # each call.  It would be more efficient to reuse
    # a req object stored in a Session.
    req = DNS.DnsRequest(name, qtype=qtype)
    resp = req.req()
    #resp.show()
    # key k: ('wayforward.net', 'A'), value v
    # FIXME: pydns returns AAAA RR as 16 byte binary string, but
    # A RR as dotted quad.  For consistency, this driver should
    # return both as binary string.
    if resp.header['tc'] == True:
      try:
        req = DNS.DnsRequest(name, qtype=qtype, protocol='tcp')
        resp = req.req()
      except IOError, x:
        raise DNSError('TCP Fallback error: ' + str(x))
    return [((a['name'], a['typename']), a['data']) for a in resp.answers]
  except IOError, x:
    raise DNSError(str(x))


class DNSSession(object):
  """A Session object has a simple cache with no TTL that is valid
   for a single "session", for example an SMTP conversation."""

  def __init__(self):
    self.cache = {}

  ## Additional DNS RRs we can safely cache.
  # We have to be careful which additional DNS RRs we cache.  For
  # instance, PTR records are controlled by the connecting IP, and they
  # could poison our local cache with bogus A and MX records.
  # Each entry is a tuple of (query_type,rr_type).  So for instance,
  # the entry ('MX','A') says it is safe (for milter purposes) to cache
  # any 'A' RRs found in an 'MX' query.
  SAFE2CACHE = frozenset((
    ('MX','MX'), ('MX','A'),
    ('CNAME','CNAME'), ('CNAME','A'),
    ('A','A'), ('AAAA','AAAA'),
    ('PTR','PTR'),
    ('NS','NS'), ('NS','A'),
    ('TXT','TXT'),
    ('SPF','SPF')))

  ## Cached DNS lookup.
  # @param name the DNS label to query
  # @param qtype the query type, e.g. 'A'
  # @param cnames tracks CNAMES already followed in recursive calls

  def dns(self, name, qtype, cnames=None):
    """DNS query.

    If the result is in cache, return that.  Otherwise pull the
    result from DNS, and cache ALL answers, so additional info
    is available for further queries later.

    CNAMEs are followed.

    If there is no data, [] is returned.

    pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF']
    post: isinstance(__return__, types.ListType)
    """
    result = self.cache.get((name, qtype))
    cname = None

    if not result:
      safe2cache = DNSSession.SAFE2CACHE
      for k, v in DNSLookup(name, qtype):
        if k == (name, 'CNAME'):
          cname = v
        if (qtype, k[1]) in safe2cache:
          self.cache.setdefault(k, []).append(v)
      result = self.cache.get((name, qtype), [])
    if not result and cname:
      if not cnames:
        cnames = {}
      elif len(cnames) >= MAX_CNAME:
        #return result    # if too many == NX_DOMAIN
        raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)
      cnames[name] = cname
      if cname in cnames:
        raise DNSError('CNAME loop')
      result = self.dns(cname, qtype, cnames=cnames)
    return result

DNS.DiscoverNameServers()

######
import threading


class HostCache:
  # ホスト名
  # 更新エンドタイム
  # 更新回数
  # アクセス回数

  def __init__(self, fname=None, maxcnt=7, maxrng=2000):
    self.maxcnt = maxcnt
    self.maxrng = maxrng
    self.rngcnt = 0
    self.cache = {}
    self.fname = fname
    self.lock = threading.Lock()
    #with self.lock
    #self.lock.acquire()
    #try:
    #
    #finally:
    #  self.lock.release()

  def hostcheck(self, hostname, dyn):
    with self.lock:
      if not self.cache.has_key(hostname):
        return None
      et, dc, ac, dy = self.cache[hostname]
      now = time.time()
      if et > now:
        ac += 1
        self.cache[hostname] = (et, dc, ac, dy)
        return True
      if ac > 0:
        ac = 0
        if (not dyn) and (dc <= self.maxcnt):
          dc += 1
      et = now + dc*24*60*60
      self.cache[hostname] = (et, dc, ac, dy)
      return False

  def hostadd(self, hostname, dyn):
    with self.lock:
      if not self.cache.has_key(hostname):
        if self.rngcnt < self.maxrng:
          et = time.time() + 24*60*60
          dc = 1
          ac = 0
          dy = int(dyn)
          self.cache[hostname] = (et, dc, ac, dy)
          self.rngcnt += 1
      else:
        et, dc, ac, dy = self.cache[hostname]
        ac += 1
        self.cache[hostname] = (et, dc, ac, dy)

  def hostdel(self, hostname):
    with self.lock:
      if self.cache.has_key(hostname):
        del self.cache[hostname]
        self.rngcnt -= 1


  def load(self, fname=None, maxcnt=0):
    if maxcnt > 0:
      self.maxcnt = maxcnt
    if fname:
      self.fname = fname
    self.rngcnt = 0
    self.cache = {}
    try:
      with open(self.fname) as fp:
        for ln in fp:
          try:
            hostname, edtime, dcnt, acnt, dyn = ln.strip().split(',')
            et = float(edtime)
            dc = int(dcnt)
            ac = int(acnt)
            dy = int(dyn)
            self.cache[hostname] = (et, dc, ac, dy)
            self.rngcnt += 1
          except:
            continue
    except:
      pass

  def save(self, fname=None, maxcnt=0):
    if maxcnt > 0:
      self.maxcnt = maxcnt
    if fname:
      self.fname = fname
    now = time.time() - self.maxcnt*24*60*60
    try:
      with open(self.fname, "w") as fp:
        for hostname, v1 in self.cache.items():
          try:
            et, dc, ac, dy = v1
            if (ac > 0) or ((ac == 0) and (et > now)):
              fp.write('%s,%f,%d,%d,%d\n' % (hostname, et, dc, ac, dy))
          except:
            continue
    except:
      pass

## (システムに合わせて修正が必要です) ##
dsnerror = HostCache(fname="/var/log/pwmail/dsnerror.log", maxcnt=10)


## ダイナミックドメインホスト
##  ドメイン部分の抽出
fqchar = '-abcdefghijklmnopqrstuvwxyz'

def DYNSplit(name, ipad):
  def DNSplit(name, tad):
    tn = name.rpartition(tad)
    if tn[1] == '':
      return None
    if tn[0] != '':
      pc = tn[0].count('.')
      if pc > 1:
        return None
    pc = tn[2].find('.')
    return tn[2][pc + 1:]

  rv = DNSplit(name, ipad)
  if rv:
    return rv

  ab = ipad.split('.')

  rab = [] + ab
  rab.reverse()
  ripad = '.'.join(rab)
  rv = DNSplit(name, ripad)
  if rv:
    return rv

  ad2 = ab[0] + '-' + ab[1]
  rad2 = ab[1] + '-' + ab[0]

  rv = DNSplit(name, rad2)
  if rv:
    pc = rv.find('.')
    prv = rv[:pc]
    if prv.find(rad2) >= 0:
      wrv = rv[pc + 1:]
      if fqdnjp.search(wrv):
        rv = wrv
    elif prv.find(ad2) >= 0:
      wrv = rv[pc + 1:]
      if fqdnjp.search(wrv):
        rv = wrv
    return rv

  rv = DNSplit(name, ad2)
  if rv:
    pc = rv.find('.')
    prv = rv[:pc]
    if prv.find(ad2) >= 0:
      wrv = rv[pc + 1:]
      if fqdnjp.search(wrv):
        rv = wrv
    elif prv.find(rad2) >= 0:
      wrv = rv[pc + 1:]
      if fqdnjp.search(wrv):
        rv = wrv
    return rv

  sad3 = ab[0].rjust(3,'0') + ab[1].rjust(3,'0') + ab[2].rjust(3,'0')
  rv = DNSplit(name, sad3)
  if rv:
    return rv

  hn = name.split('.')
  if (hn[0].lstrip(fqchar) == ab[3]) and (hn[1].lstrip(fqchar) == ab[2]):
    if (hn[2].lstrip(fqchar) in ab[0:2]):
      if (hn[3].lstrip(fqchar) in ab[0:2]):
        rv = '.'.join(hn[4:])
        if fqdnjp.search(rv):
          return rv
      rv = '.'.join(hn[3:])
      if fqdnjp.search(rv):
        return rv
    rv = '.'.join(hn[2:])
    return rv

  rv = '.'.join(hn[1:])
  return rv

## ダイナミックドメインホスト
##  ドメイン部分の抽出テスト用ログ
class DYNDomainCheck:
  # ドメイン名
  # フルホスト名
  # IPアドレス
  # アクセス回数

  def __init__(self, fname=None, maxrng=3000):
    self.maxrng = maxrng
    self.rngcnt = 0
    self.cache = {}
    self.fname = fname
    self.lock = threading.Lock()
    #with self.lock
    #self.lock.acquire()
    #try:
    #
    #finally:
    #  self.lock.release()

  def DYNdomain(self, hname, ipad):
    dname = DYNSplit(hname, ipad)
    with self.lock:
      if not self.cache.has_key(dname):
        if self.rngcnt < self.maxrng:
          ac = 1
          self.rngcnt += 1
          self.cache[dname] = (hname, ipad, ac)
        return dname
      wn, wad, ac = self.cache[dname]
      ac += 1
      self.cache[dname] = (hname, ipad, ac)
      return dname

  def load(self, fname=None):
    if fname:
      self.fname = fname
    self.rngcnt = 0
    self.cache = {}
    try:
      with open(self.fname) as fp:
        for ln in fp:
          try:
            dname, hname, ipad, acnt = ln.strip().split(',')
            ac = int(acnt)
            self.cache[dname] = (hname, ipad, ac)
            self.rngcnt += 1
          except:
            continue
    except:
      pass

  def save(self, fname=None):
    if fname:
      self.fname = fname
    try:
      with open(self.fname, "w") as fp:
        for dname, v1 in self.cache.items():
          try:
            hname, ipad, ac = v1
            fp.write('%s,%s,%s,%d\n' % (dname, hname, ipad, ac))
          except:
            continue
    except:
      pass

## (システムに合わせて修正が必要です) ##
dyndcheck = DYNDomainCheck(fname="/var/log/pwmail/dyndcheck.log")



## ホワイトリストチェック
class WhitelistCheck:
  # connect  : CBdomain
  # hello    : HBdomain
  # mailfrom : FBdomain
  # モード   : 送信者・差出人のアドレス管理
  #          : d(ドメイン違い), u(ユーザ違い), n(同アドレス)
  # return   : モード + None(nolist)

  def __init__(self, fname=None):
    self.rngcnt = 0
    self.cache = {}
    self.fname = fname
    #with self.lock
    #self.lock.acquire()
    #try:
    #
    #finally:
    #  self.lock.release()

  def WhiteCheck(self, CBdomain, HBdomain, FBdomain):
    if not self.cache.has_key((CBdomain, HBdomain, FBdomain)):
      return None
    return self.cache[(CBdomain, HBdomain, FBdomain)]

  def load(self, fname=None):
    if fname:
      self.fname = fname
    self.rngcnt = 0
    self.cache = {}
    try:
      with open(self.fname) as fp:
        for ln in fp:
          try:
            CBdomain, HBdomain, FBdomain, Amode = ln.strip().split(',')
            self.cache[(CBdomain, HBdomain, FBdomain)] = Amode
            self.rngcnt += 1
          except:
            continue
    except:
      pass

## (システムに合わせて修正が必要です) ##
wlistcheck = WhitelistCheck(fname="/usr/local/pwmail/whitelist.dat")


###外部###############

# Shift JIS UTF8 コードをunicodeに編集
def msg_cnvt(s):
  u = u""
  for enc1 in ('utf8', 'cp932'):
    try:
      u = unicode(s, enc1)
    except UnicodeDecodeError:
      continue
    break
  if u == u"":
    u = unicode(s, 'utf8', 'replace')
  return u

###
### Milter.utils コピーを修正
def parse_header(val):
  """Decode headers gratuitously encoded to hide the content.
  """
  try:
    h = decode_header(val)
    if not len(h):
      return val
    u = []
    for s, enc in h:
      if enc:
        try:
          u.append(unicode(s, enc))
        except LookupError:
          u.append(unicode(s))
      else:
        if isinstance(s, unicode):
          u.append(s)
        else:
          u.append(msg_cnvt(s))
    u = ''.join(u)
    for enc in ('us-ascii', 'iso-8859-1', 'utf8'):
      try:
        return u.encode(enc)
      except UnicodeError:
        continue
  except UnicodeDecodeError:
    pass
  except LookupError:
    pass
  except email.Errors.HeaderParseError:
    pass
  return val

# FQDNシンタックスチェック
def fqdncheck(s):
  if fqdn.match(s):
    if s.find('-.') < 0:
      return True
  return False

# 受信ローカルドメインチェック
def my_domain_check(td):
  tl = len(td)
  for d, l in my_domainlist:
    if tl == l:
      if td == d:
        return True
    elif tl > l:
      if td.endswith(d):
        pw = td[tl - l -1]
        if (pw == '.') or (pw == '@'):
          return True
  return False

# ドメイン部分が同じかをチェック
def eq_domain_check(td, d):
  tl = len(td)
  l = len(d)
  if tl == l:
    if td == d:
      return True
  elif tl > l:
    if td.endswith(d):
      if td[tl - l -1] == '.':
        return True
  return False

# ドメイン部分が抽出
def DomainSplit(name):
  pos = name.find('.')
  dmain = name[pos + 1:]
  if fqdnjp.search(dmain):
    return dmain
  return None


class myMilter(Milter.Base):

  def __init__(self):  # A new instance with each new connection.
    self.id = Milter.uniqueID()  # Integer incremented with each call.

  # SMTP エンベロープを編集
  def msg_connect(self, rcpt_addr):
    if not self.Cname:
      Cname = u""
    else:
      Cname = msg_cnvt(self.Cname)
    if not self.CBdomain:
      CBdomain = u""
    else:
      CBdomain = msg_cnvt(self.CBdomain)

    if not self.IP:
      IP = u""
    else:
      IP = msg_cnvt(self.IP)

    if not self.Hname:
      Hname = u""
    else:
      Hname = msg_cnvt(self.Hname)
    if not self.HBdomain:
      HBdomain = u""
    else:
      HBdomain = msg_cnvt(self.HBdomain)

    if not self.Fname:
      Fname = u""
    else:
      Fname = msg_cnvt(self.Fname)
    if not self.FBdomain:
      FBdomain = u""
    else:
      FBdomain = msg_cnvt(self.FBdomain)

    if not rcpt_addr:
      wrcpt_addr = u""
    else:
      wrcpt_addr = msg_cnvt(rcpt_addr)
    try:
      msg = (u"connect from %s(%s) at %s\n"
             "helo: %s(%s)\n"
             "mail from: %s(%s)\n" %
             (Cname, CBdomain, IP, Hname, HBdomain, Fname, FBdomain))
      if rcpt_addr:
        msg += (u"rcpt to: " + wrcpt_addr + "\n")
      return msg
    except:
      self.log_warning("msg_connect:", traceback.format_exc())
      return u""

  # SMTP メールヘッダを編集
  def msg_Header(self):
    if not self.HFrom:
      hfrom = u""
    else:
      hfrom = msg_cnvt(self.HFrom)
    if not self.Subject:
      subject = u""
    else:
      subject = msg_cnvt(self.Subject)
    if not self.HDate:
      hdate = u""
    else:
      hdate = msg_cnvt(self.HDate)
    if not self.HMid:
      hmid = u""
    else:
      hmid = msg_cnvt(self.HMid)
    try:
      return (u"Header-From: %s\n"
                "Header-Subject: %s\n"
                "Header-Date: %s\n"
                "Message-ID: %s\n" % (hfrom, subject, hdate, hmid))
    except:
      self.log_warning("msg_Header:", traceback.format_exc())
      return u""


  # ログメール システム用(SMTP エンベロープ)
  def send_admin0(self, subject, ERlevel):
    encoding = "ISO-2022-JP"

    body = self.msg_connect(None) + u"X-PWmail: %s\n" % self.PWmsg
    mf = parse_addr(to_pwadmin)
    un = mf[0]
    pos = un.find('+')
    if pos != -1:
      un = un[:pos]
    from_addr = un + "+admin@" + mf[1]
    to_addr = un + "+" + ERlevel + "@" + mf[1]

    try:
      msg = MIMEText(body.encode(encoding, 'replace'), 'plain', encoding)
      msg['Subject'] = Header(subject, encoding)
      msg['From'] = from_addr
      msg['To'] = to_addr
      msg['Date'] = formatdate()
    except:
      self.log_warning('send_admin1 MIMEText:', traceback.format_exc())
      return

    s = smtplib.SMTP('localhost', sendmail_port, 'localhost')
    try:
      s.sendmail(from_addr, [to_pwadmin], msg.as_string())
    except:
      self.log_warning('send_admin1 sendmail:', traceback.format_exc())
    s.quit()


  # ログメール システム用(SMTP エンベロープ)
  def send_admin1(self, rcpt_addr, subject, ERlevel):
    encoding = "ISO-2022-JP"

    body = self.msg_connect(rcpt_addr) + u"X-PWmail: %s\n" % self.PWmsg
    mf = parse_addr(rcpt_addr)
    un = mf[0]
    pos = un.find('+')
    if pos != -1:
      un = un[:pos]
    from_addr = un + "+admin@" + mf[1]
    to_addr = un + "+" + ERlevel + "@" + mf[1]

    try:
      msg = MIMEText(body.encode(encoding, 'replace'), 'plain', encoding)
      msg['Subject'] = Header(subject, encoding)
      msg['From'] = from_addr
      msg['To'] = to_addr
      msg['Date'] = formatdate()
    except:
      self.log_warning('send_admin1 MIMEText:', traceback.format_exc())
      return

    s = smtplib.SMTP('localhost', sendmail_port, 'localhost')
    try:
      s.sendmail(from_addr, [to_pwadmin], msg.as_string())
    except:
      self.log_warning('send_admin1 sendmail:', traceback.format_exc())
    s.quit()


  # ログメール システム用
  #(SMTP エンベロープ・メールヘッダ)
  def send_admin2(self, rcpt_addr, subject, ERlevel):
    encoding = "ISO-2022-JP"

    body = self.msg_connect(rcpt_addr) + self.msg_Header() + \
           u"X-PWmail: %s\n" % (self.PWmsg)
    mf = parse_addr(rcpt_addr)
    un = mf[0]
    pos = un.find('+')
    if pos != -1:
      un = un[:pos]
    from_addr = un + "+admin@" + mf[1]
    to_addr = un + "+" + ERlevel + "@" + mf[1]

    try:
      msg = MIMEText(body.encode(encoding, 'replace'), 'plain', encoding)
      msg['Subject'] = Header(subject, encoding)
      msg['From'] = from_addr
      msg['To'] = to_addr
      if not self.HDate:
        msg['Date'] = formatdate()
      else:
        msg['Date'] = self.HDate
    except:
      self.log_warning('send_admin2 MIMEText:', traceback.format_exc())
      return

    s = smtplib.SMTP('localhost', sendmail_port, 'localhost')
    try:
      s.sendmail(from_addr, [to_pwadmin], msg.as_string())
    except:
      self.log_warning('send_admin2 sendmail:', traceback.format_exc())
    s.quit()

  # ログメール システム&ユーザ用
  #(SMTP エンベロープ・メールヘッダ)
  def send_admin3(self, rcpt_addr, subject, ERlevel):
    encoding = "ISO-2022-JP"

    body = self.msg_connect(rcpt_addr) + self.msg_Header() + \
           u"X-PWmail: %s\n" % (self.PWmsg)
    mf = parse_addr(rcpt_addr)
    un = mf[0]
    pos = un.find('+')
    if pos != -1:
      un = un[:pos]
    from_addr = un + "+admin@" + mf[1]
    to_addr = un + "+" + ERlevel + "@" + mf[1]

    try:
      msg = MIMEText(body.encode(encoding, 'replace'), 'plain', encoding)
      msg['Subject'] = Header(subject, encoding)
      msg['From'] = from_addr
      msg['To'] = to_addr
      if not self.HDate:
        msg['Date'] = formatdate()
      else:
        msg['Date'] = self.HDate
    except:
      self.log_warning('send_admin3 MIMEText:', traceback.format_exc())
      return

    s = smtplib.SMTP('localhost', sendmail_port, 'localhost')
    try:
      s.sendmail(from_addr, [to_addr, to_pwadmin], msg.as_string())
    except:
      self.log_warning('send_admin3 sendmail:', traceback.format_exc())
    s.quit()


###外部????###############
  # nameがipadを示しているかをチェック
  # nameがホスト名の場合にアドレスが存在するかをチェック
  # nameがドメイン名の場合にMXレコードが存在するかをチェック
  # DNSが正常かをチェック
  def DNSCheck(self, name, ipad):
    adok = False
    ad4ok = False
    adng = False

    mxok = False
    mx4ok = False
    mx6ok = False
    mxng = False

    rv = False
    rm = rt = ''

    s = DNSSession()
    try:
      ads = s.dns(name, 'A')
      if len(ads) > 0:
        ad4ok = True
        for a in ads:
          if a == ipad:
            adok = True
          elif not a:
            adng = True

      mxr = s.dns(name, 'MX')
      if len(mxr) > 0:
        for v, n in mxr:
          ads = s.dns(n, 'A')
          if len(ads) > 0:
            mx4ok = True
            for a in ads:
              if a == ipad:
                mxok = True
              elif not a:
                mxng = True
          else:
            ads = s.dns(n, 'AAAA')
            if len(ads) > 0:
              mx6ok = True
              for a in ads:
                if not a:
                  mxng = True
            else:
              mxng = True

      if adng or mxng or ((not ad4ok) and (not mx4ok)):
        rv = None
      else:
        if adok:
          rv = True
          rm += 'A'
        if mxok:
          rv = True
          rm += 'M'

      if ad4ok:
        rt += 'A'
      if mx4ok:
        rt += 'M'
      if mx6ok:
        rt += '6'

    except:
      self.log_warning("DNS:", traceback.format_exc())
      rv = None
      rm = rt = 'DNS'
    return (rv, rm, rt)


  def HostCheck(self, Hname, ipad, dyn):
    ## ホスト名部分のキープ ####################################
    hcheck = dsnerror.hostcheck(Hname, dyn)
    if hcheck:
      ipok = (None, '', '')
    else:
      ipok = self.DNSCheck(Hname, ipad)
      if ipok[0] == None:
        if hcheck == None:
          dsnerror.hostadd(Hname, dyn)
      elif hcheck == False:
        dsnerror.hostdel(Hname)
    return ipok


#  @Milter.noreply
  def connect(self, IPname, family, hostaddr):
    # 設定する変数
    ## self.IPname 未使用
    # self.Cname
    # self.Cfqdn
    # self.Cdynip
    # self.Cipok
    # self.Sabort
    #
    # REJECTの使用可能なエラーコマンドとステイタス
    # 554  Transaction failed (Or, in the case of a connection-opening
    #                          response, "No SMTP service here")
    # X.3.5 System incorrectly configured

    self.log("connect from %s at %s" % (IPname, hostaddr))
    # Ini Setup
    self.PWmsg = ""
    self.PWmode = ""
    self.Sabort = None

    if hostaddr and len(hostaddr) > 0:
      self.IP = hostaddr[0]
    else:
      self.log_critical("REJECT: connect attacks")
      self.setreply('554', '5.3.5', 'Banned for connect attacks')
      return Milter.REJECT

    self.IP = hostaddr[0]
##    self.port = hostaddr[1]
    self.Cname = IPname.lower()  # Name from a reverse IP lookup
    if fqdncheck(self.Cname):
      self.Cfqdn = True
      self.Cdynip = dynip(self.Cname, self.IP)
      self.Cipok = self.HostCheck(IPname, self.IP, self.Cdynip)
      if (not self.Cipok[0]) and (self.Cname != IPname):
        self.Cipok = self.HostCheck(self.Cname, self.IP, self.Cdynip)

      ## 現在postfix&Milter側でREJECTされている
      if not self.Cipok[0]:
        self.log_critical("REJECT: connect attacks: DNS Host not found.")
        self.setreply('554', '5.3.5', 'Banned for connect attacks: DNS Host not found.')
        return Milter.REJECT

      if self.Cdynip:
        self.CBdomain = dyndcheck.DYNdomain(self.Cname, self.IP)
      else:
        self.CBdomain = DomainSplit(self.Cname)
        if not self.CBdomain:
          self.CBdomain = self.Cname
    else:
      self.Cfqdn = False
      self.Cipok = (None, '', '')
      self.Cdynip = None
      self.CBdomain = None

    self.Hname = None
    self.Hmyd = None
    self.Hipok = (None, '', '')
    self.Hrelay = None
    return Milter.CONTINUE


#  @Milter.noreply
  def hello(self, heloname):
    # 設定する変数
    ## self.heloname 未使用
    # self.Hname
    # self.Hfqdn
    # self.Hmyd
    # self.Hdynip
    # self.Hipok
    #
    # REJECTの使用可能なエラーコマンドとステイタス
    # 504  Command parameter not implemented
    # X.5.1   Invalid command
    # X.5.1  不正なコマンド
    # X.5.2   Syntax error
    # X.5.2  構文エラー

    hname = msg_cnvt(heloname)
    self.log("HELO", hname.encode('utf8', 'replace'))

    # Ini Setup
    self.PWmsg = ""
    self.PWmode = ""
    if self.Cfqdn:
      if self.Cdynip:
        self.PWmsg = self.PWmsg + "Cdynip "             # yellow
      if self.Cipok[0] == None:
        self.PWmsg = self.PWmsg + "ngCipok "            # error
      elif self.Cipok[0] == False:
        self.PWmsg = self.PWmsg + "noCipok "            # error gray
    else:
      self.PWmsg = self.PWmsg + "noCfqdn "

    self.Hname = heloname.lower()
    self.Hmyd = None
    self.Hipok = (None, '', '')
    self.Hrelay = None

    if not fqdncheck(self.Hname):
      #
      if ip4re.match(self.Hname):
        hnameip = self.Hname
        if hnameip[0] == '[':
          hnameip = hnameip[1:-1]
        if hnameip == my_hnameip:
          self.Hmyd = True
          self.PWmsg = self.PWmsg + "Hmyd "             # error

      # 不正なコマンド として REJECT する必要性がある
      elif not hostre.match(self.Hname):
        self.log_critical("REJECT: Helo command Syntax error")
        self.setreply('554', '5.5.2',
                      '<%s>: Helo command Syntax error' % (heloname))
        return Milter.REJECT
      #
      self.Hfqdn = False
      self.Hdynip = None
      self.HBdomain = None
      self.PWmsg = self.PWmsg + "noHfqdn "              # error gray
      return Milter.CONTINUE


    self.Hfqdn = True
    self.HBdomain = self.Hname
    self.Hrelay = False

    self.Hmyd = my_domain_check(self.Hname)
    if self.Hmyd:
      self.PWmsg = self.PWmsg + "Hmyd "               # error
      return Milter.CONTINUE

    self.Hdynip = dynip(self.Hname, self.IP)
    if self.Hdynip:
      self.PWmsg = self.PWmsg + "Hdynip "             # yellow
      if (not self.Cdynip) or (self.Cname != self.Hname):
        self.HBdomain = dyndcheck.DYNdomain(self.Hname, self.IP)

    if self.Cfqdn:
      if self.Cname == self.Hname:
        self.Hipok = self.Cipok
        self.HBdomain = self.CBdomain
        if self.Hipok[0] == None:
          self.PWmsg = self.PWmsg + "ngHipok "      # error
        elif self.Hipok[0] == False:
          self.PWmsg = self.PWmsg + "noHipok "      # error gray
        return Milter.CONTINUE

      if self.Cipok[0]:
        if self.Hdynip:
          if eq_domain_check(self.Cname, self.HBdomain): #type2
            wHipok = self.HostCheck(self.Hname, self.IP, self.Hdynip)
            if wHipok[0]:
              self.Hipok = wHipok
            else:
              self.Hipok = (True, self.Cipok[1], wHipok[2])
              # 同一ドメイン内のリレー及び記述ミスの回避
              wHipok = self.HostCheck(self.HBdomain, self.IP, False)
              if wHipok[0]:
                self.Hipok = wHipok
            return Milter.CONTINUE
        else:
          if eq_domain_check(self.Cname, self.Hname): #type1
            wHipok = self.HostCheck(self.Hname, self.IP, self.Hdynip)
            if wHipok[0]:
              self.Hipok = wHipok
            else:
              self.Hipok = (True, self.Cipok[1], wHipok[2])
            return Milter.CONTINUE

          # 同一ドメイン内のリレー及び記述ミスの回避
          Hdmain = DomainSplit(self.Hname)
          if Hdmain:
            if eq_domain_check(self.Cname, Hdmain): #type2
              self.HBdomain = Hdmain
              wHipok = self.HostCheck(Hdmain, self.IP, False)
              if wHipok[0]:
                self.Hipok = wHipok
              else:
                self.Hipok = (True, self.Cipok[1], wHipok[2])
              return Milter.CONTINUE
        self.Hrelay = True

    self.Hipok = self.HostCheck(self.Hname, self.IP, self.Hdynip)
    # 送信ホスト名の記述ミスの回避
    if (not self.Hipok[0]):
      if (self.Hname != heloname):
        self.Hipok = self.HostCheck(heloname, self.IP, self.Hdynip)

    if self.Hdynip:
      if not self.Hipok[0]:
        wHipok = self.HostCheck(self.HBdomain, self.IP, False)
        if wHipok[0]:
          self.Hipok = wHipok
    else:
      if self.Hipok[2].find('M') < 0:
        Hdmain = DomainSplit(self.Hname)
        if Hdmain:
          self.HBdomain = Hdmain
          # 送信ホスト名の記述ミスの回避
          wHipok = self.HostCheck(self.HBdomain, self.IP, False)
          if wHipok[0]:
            self.Hipok = wHipok

    if self.Hipok[0] == None:
      self.PWmsg = self.PWmsg + "ngHipok "            # error
    elif self.Hipok[0] == False:
      self.PWmsg = self.PWmsg + "noHipok "            # error gray
    if self.Hrelay:
      self.PWmsg = self.PWmsg + "Hrelay "             # yellow

    return Milter.CONTINUE


#  @Milter.noreply
  def envfrom(self, mailfrom, *str):
    def fromcheck(wmailfrom):
      # 設定する変数
      # self.Fname
      # self.Fad
      # self.Fd
      # self.Ffqdn
      # self.Fmyd
      # self.Fdynip
      # self.Fipok
      self.Fname = wmailfrom
      self.Fad = ''
      self.Fd = ''
      self.Ffqdn = False
      self.Fmyd = False
      self.Fdynip = None
      self.Fipok = (None, '', '')

      if wmailfrom == '<>':
        self.Ffqdn = None
        return None

      mb = parseaddr(wmailfrom)
      if (mb[1] == None) or (mb[1] == ''):
        self.PWmsg = self.PWmsg + "noFfqdn "              # error
        return None

      self.Fad = mb[1]
      mf = parse_addr(mb[1])
      if len(mf) != 2:
        self.PWmsg = self.PWmsg + "noFfqdn "              # error
        return None

      self.Fd = mf[1].lower()
      if not fqdncheck(self.Fd):
        self.PWmsg = self.PWmsg + "noFfqdn "              # error
        return None

      self.Ffqdn = True
      self.FBdomain = self.Fd

      self.Fmyd = my_domain_check(self.Fd)
      if self.Fmyd:
        self.PWmsg = self.PWmsg + "Fmyd "               # error
        return None

      self.Fdynip = dynip(self.Fd, self.IP)
      if self.Fdynip:
        # 通常は不正なコマンド として REJECT する必要性がある
        self.PWmsg = self.PWmsg + "Fdynip "             # error
        self.FBdomain = dyndcheck.DYNdomain(self.Fd, self.IP)

      self.Fipok = self.HostCheck(self.Fd, self.IP, self.Fdynip)
      if self.Fipok[0] == None:
        self.PWmsg = self.PWmsg + "ngFipok "            # error not(MX/A)
      elif self.Fipok[0] == False:
        self.PWmsg = self.PWmsg + "noFipok "            # OK
      else: # True
        return False

      # 同ドメイン内のリレーチェック(許可)
      if self.Hfqdn:
        if eq_domain_check(self.Hname, self.FBdomain): #type1
          return False
        if not self.Fdynip:
          Fdmain = DomainSplit(self.Fd)
          if Fdmain:
            if eq_domain_check(self.Hname, Fdmain): #type2
              self.FBdomain = Fdmain
              return False
        return True

      return False


    # 設定する変数
    # self.Fname
    # self.Fad
    # self.Fd
    # self.Ffqdn
    # self.Fmyd
    # self.Fdynip
    # self.Fipok
    # self.Relay
    #
    # REJECTの使用可能なエラーコマンドとステイタス
    # 550
    # 555  MAIL FROM/RCPT TO parameters not recognized or not implemented
    # 555  MAIL FROM/RCPT TO のパラメータが認められていないか実装されていません
    # X.1.8   Bad sender's system address
    # X.1.8  正しくない送り手のシステムのアドレス
    # X.4.3   Directory server failure
    # X.4.3  ディレクトリサーバの失敗
    # X.4.4   Unable to route
    # X.4.4  ルーティング不能

    self.log("mail from:", mailfrom, *str)

    # Ini Setup
    self.Rname = []  # list of recipients

    self.Relay = fromcheck(mailfrom)
    if self.Relay:
      self.PWmsg = self.PWmsg + "relay "              # yellow

    ###
    ### 送信はsubmission経由を前提に作成しています。
    ###

    ### Abort 条件の追加 以降のフィルターは、
    ###   rcptコマンド迄にREJECTするとハングするかheloコマンドからリトライするサーバがあります。
    ###
    ### 全ての宛先をログに残す場合は、エラーコマンドの 550 を 450 に修正して下さい。
    ###   この場合には、何らかの方法(DB等)で検証して2回目からは 550 で対応する事を進めます。

    ## Abort 条件の追加
    ## 送信者ドメイン等でIP確定する場合には、connect Helo コマンドでのチェックはしない。
    ##   「マルチドメインサーバに対する対応と初心者の設定ミスに対しての対応」
    if not self.Fipok[0]:
      ### 送信サーバがホスト名等でIP確定出来ない
      ### 現在connectコマンド側で処理されている。
      #if (self.Cfqdn) and (not self.Cipok[0]):
      #  self.setreply('550', '5.1.8',
      #                '(%s:%s): connectHost rejected: DNS Host not found.' %
      #                (self.Cname,self.IP))
      #  self.log_error('550', 'connectHost rejected: DNS Host not found.')
      #  ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
      #  #self.send_admin0(u"送信サーバがホスト名等でIP確定出来ない", "abort0")
      #  #self.send_admin1(recipient, u"送信サーバがホスト名等でIP確定出来ない", "abort0")
      #  self.Sabort = False
      #  return Milter.REJECT

      ## 基本は、Helo コマンドの FQDN である
      if not self.Hfqdn:
        self.setreply('550', '5.5.2',
                      '<%s>: Helo command rejected: '
                      'need fully-qualified hostname.' % (self.Hname))
        self.log_error('550', 'Helo command rejected: need fully-qualified hostname.')
        ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
        #self.send_admin0(u"Heloコマンドのホスト名は、FQDNでない", "abort0")
        #self.send_admin1(recipient, u"Heloコマンドのホスト名は、FQDNでない", "abort0")
        self.Sabort = False
        return Milter.REJECT

      ### Helo コマンドのドメイン名等でIP確定する場合は、connectHostのチェックはしない。
      if (self.Hipok[0] == None) or (self.Hipok[0] == False) and (not self.Cipok[0]):
        ### Helo コマンドのドメイン名等でIP確定出来ない
        self.setreply('550', '5.1.8',
                      '(%s:%s): Helo command rejected: DNS Host(MX) not found.' %
                      (self.Hname,self.IP))
        self.log_error('550', 'Helo command rejected: DNS Host(MX) not found.')
        ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
        #self.send_admin0(u"Helo コマンドのドメイン名等でIP確定出来ない", "abort0")
        #self.send_admin1(recipient, u"Helo コマンドのドメイン名等でIP確定出来ない", "abort0")
        self.Sabort = False
        return Milter.REJECT

    ### Helo コマンドのホスト名が受信ドメインである
    ### 送信がsubmission経由の場合には、ありえない事です。
    if self.Hmyd:
      self.setreply('550', '5.5.2',
                    '<%s>: Helo command rejected: Breach of Local Policy.' %
                    (self.Hname))
      self.log_critical('550',
                        'Helo command rejected: Breach of Local Policy.')
      ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
      self.send_admin0(u"送信ホスト名がマイドメイン", "abort")
      #self.send_admin1(recipient, u"送信ホスト名がマイドメイン", "abort")
      self.Sabort = False
      return Milter.REJECT

    if self.Fname != '<>': # (postmaster) OK

      ## SenderHost は、FQDN記述が必要である(ダイナミックDNSでは、返信不能の可能性がある)
      if (self.Ffqdn == False) or self.Fdynip:
        self.setreply('550', '5.5.2',
                      '%s: Sender address rejected: '
                      'need fully-qualified address.' % (self.Fname))
        self.log_critical('550',
                          'Sender address rejected: '
                          'need fully-qualified address.')
        ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
        self.send_admin0(u"送信者名エラー", "abort")
        #self.send_admin1(recipient, u"送信者名エラー", "abort")
        self.Sabort = False
        return Milter.REJECT

      ### SenderHostが返信不能である
      ##  リトライで真意の確認をする必要性があるのか?
      ##  その場合は、Message-ID の確認が必要と思われる
      if self.Fipok[0] == None:
        self.setreply('550', '5.4.3',
                      '%s: Sender address rejected: Breach of Domain.' %
                      (self.Fname))
        self.log_critical('550',
                          'Sender address rejected: Breach of Domain.')
        ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
        self.send_admin0(u"送信者名がDNSエラー", "abort")
        #self.send_admin1(recipient, u"送信者名がDNSエラー", "abort")
        self.Sabort = False
        return Milter.REJECT

      ### SenderHostが受信ドメインである
      ### 送信がsubmission経由の場合には、ありえない事です。
      if self.Fmyd:
        self.setreply('550', '5.5.2',
                      '%s: Sender address rejected: Breach of Local Policy.' %
                      (self.Fname))
        self.log_critical('550',
                          'Sender address rejected: Breach of Local Policy.')
        ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
        self.send_admin0(u"送信者がマイドメイン", "abort")
        #self.send_admin1(recipient, u"送信者がマイドメイン", "abort")
        self.Sabort = False
        return Milter.REJECT

    return Milter.CONTINUE


#  @Milter.noreply
  def envrcpt(self, recipient, *str):
    # 設定する変数
    # self.Rname
    #
    # REJECTの使用可能なエラーコマンドとステイタス
    # 550
    # 553  Requested action not taken: mailbox name not allowed
    #        (e.g.,mailbox syntax incorrect)
    # X.1.1   Bad destination mailbox address
    # X.1.1  正しくない宛先メールボックスのアドレス
    # X.7.1   Delivery not authorized, message refused
    # X.7.1  配送が正当と認められず、メッセージが拒絶された
    # X.2.2   Mailbox full
    # X.2.2  メールボックスが一杯
    #
    # 450  Requested mail action not taken: mailbox unavailable
    #        (e.g.,mailbox busy or temporarily blocked for policy reasons)
    # 450  メールボックスが利用できないため(例えば、メールボックスが使用中
    #      であったり、ポリシーの理由で一時的にブロックされたなど)要求された
    #      メールの動作ができません
    # X.2.1   Mailbox disabled, not accepting messages
    # X.2.1  メールボックスが利用不可能、メッセージを受け取らない
    # X.2.2   Mailbox full
    # X.2.2  メールボックスが一杯

    self.log("rcpt to:", recipient, ":", *str)
    self.Rname.append(recipient)

    ###
    ### 送信はsubmission経由を前提に作成しています。
    ###

    ## 外部からはロギング用メールアドレスを拒否する。
    ## 拒否しない場合、コメントアウトする
    if recipient.lower() == to_pwadmin:
      self.setreply('550', '5.1.1',
                    '<%s>: Recipient address rejected: User unknown.' %
                    (recipient))
      self.log_critical('550', 'Recipient address rejected: User unknown.')
      ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
      self.send_admin1(recipient, u"ロギング用メールアドレス", "abort")
      self.Sabort = False
      return Milter.REJECT

    ## 外部からは拡張アドレスを拒否する。
    ## ユーザ名に+が入っているとエラー処理(postfix recipient_delimiter = +)の設定で修正必要
    ## 拒否しない場合、コメントアウトする
    if recipient.find(recipient_delimiter) != -1:
      self.setreply('550', '5.1.1',
                    '<%s>: Recipient address rejected: User unknown.' %
                    (recipient))
      self.log_critical('550', 'Recipient address rejected: User unknown.')
      ## ロギング用メールアドレスに記録を残さない場合、下記1行をコメントアウトする
      self.send_admin1(recipient, u"ユーザ名エラー", "abort")
      self.Sabort = False
      return Milter.REJECT

    return Milter.CONTINUE


#  @Milter.noreply
  def data(self):
    #
    # REJECTの使用可能なエラーコマンドとステイタス
    # 550 rejections for policy reasons
    # 450 rejections for policy reasons
    # 550 450 ポリシーによる理由での拒否
    # X.7.1   Delivery not authorized, message refused
    # X.7.1  配送が正当と認められず、メッセージが拒絶された
    #
    # X.1.8   Bad sender's system address
    # X.1.8  正しくない送り手のシステムのアドレス
    # X.4.3   Directory server failure
    # X.4.3  ディレクトリサーバの失敗
    # X.4.4   Unable to route
    # X.4.4  ルーティング不能
    # X.5.2   Syntax error
    # X.5.2  構文エラー

##    self.log("data")
    self.log_debug("data")

    # Ini Setup
    self.FromAD = []
    self.HFrom = None
    self.HDate = None
    self.Subject = None
    self.HMid = None
    self.HList = None

    return Milter.CONTINUE


#  @Milter.noreply
  def header(self, name, hval):
    ### self.log_debug("header:%s: %s" % (name,hval))

    nbuf = name.lower()
    if nbuf == "from":
      ms = []
      adbuf = hval.split(',')
      for ad in adbuf:
        ma = parseaddr(ad)
        mn = parse_header(ma[0])
        ms.append(mn + ' <' + ma[1] + '>')
        self.FromAD.append((mn, ma[1]))
      mf = ",".join(ms)
      if not self.HFrom:
        self.HFrom = mf
      else:
        self.HFrom = self.HFrom + ',' + mf
      self.log_debug("Header-From-B:", hval)
      self.log("Header-From:", mf)
    elif nbuf == "date":
      self.HDate = hval
    elif nbuf == "subject":
      self.Subject = parse_header(hval)
      self.log_debug("Subject-B:", hval)
      self.log("Subject:", self.Subject)
    elif nbuf == "message-id":
      self.log("Message-ID:", hval)
      self.HMid = hval
    elif nbuf.startswith("list-"):
      self.HList = True

    return Milter.CONTINUE


#  @Milter.noreply
  def eoh(self):
    def whitech():
      CBdomain = ''
      HBdomain = ''
      if not self.Fipok[0]:
        if self.Relay:
          HBdomain = self.HBdomain
        if self.Hrelay:
          HBdomain = self.HBdomain
          CBdomain = self.CBdomain

      wlch = wlistcheck.WhiteCheck(CBdomain, HBdomain, self.FBdomain)
      if wlch:
        self.PWmsg = self.PWmsg + "White "
        if wlch == 'd':
          return True
        elif wlch == 'u':
          if self.HFdnok:
            return True
        elif wlch == 'n':
          if self.HFadok:
            return True
      return False


##    self.log("eoh")
    self.log_debug("eoh")
### 日付フォーマットチェックが必要
    if not self.HDate:
      self.PWmsg = self.PWmsg + "noHDate "              # error
    if not self.HMid:
      self.PWmsg = self.PWmsg + "noHMid "               # yellow (self.Cdynip,self.Hdynip,self.Relay) True:Error

    # Ini Setup
    self.HFromAD = None
    self.HFfqdn = None
    self.HFmyd = False
    self.HFdynip = None
    self.HFipok = (None, '', '')
    self.HFdnok = None
    self.HFadok = None

    if len(self.FromAD) != 1:
      self.HFadER = True
      self.PWmsg = self.PWmsg + "HFadER "
    else:
      self.HFadER = False
      ad = self.FromAD[0][1]
      self.HFromAD = ad
### アマゾン特別対応
      if (ad[0] == "'") and (ad[-1] == "'"):
        ad = ad[1:-1]
### アマゾン特別対応
      if (ad == None) or (ad == ''):
        self.HFfqdn = False
        self.PWmsg = self.PWmsg + "noHFfqdn "
      else:
        mf = parse_addr(ad)
        if len(mf) != 2:
          self.HFfqdn = False
          self.PWmsg = self.PWmsg + "noHFfqdn "
        else:
          dn = mf[1].lower()
          if not fqdncheck(dn):
            self.HFfqdn = False
            self.PWmsg = self.PWmsg + "noHFfqdn "
          else:
            self.HFfqdn = True
            self.HFmyd = my_domain_check(dn)
            if self.HFmyd:
              self.PWmsg = self.PWmsg + "HFmyd "
            else:
              self.HFdynip = dynip(dn, self.IP)
              if self.HFdynip:
                self.PWmsg = self.PWmsg + "HFdynip "

              self.HFipok = self.HostCheck(dn, self.IP, self.HFdynip)
              if self.HFipok[0] == None:
                self.PWmsg = self.PWmsg + "ngHFipok "
              elif self.HFipok[0] == False:
                self.PWmsg = self.PWmsg + "noHFipok "

              if self.Ffqdn:
                if eq_domain_check(self.Fd, dn): #type1
                  self.HFdnok = True
                else:
                  pos = dn.find('.')
                  sdn = dn[pos + 1:]
                  if fqdnjp.search(sdn):
                    if eq_domain_check(self.Fd, sdn): #type2
                      self.HFdnok = True
                if not self.HFdnok:
                  self.HFdnok = False
                  self.PWmsg = self.PWmsg + "noHFdnok "

                if self.Fad == ad:
                  self.HFadok = True
                else:
                  self.HFadok = False
                  self.PWmsg = self.PWmsg + "noHFadok "

    if self.HList:
      self.PWmsg = self.PWmsg + "List "


    ### 日付は、必要事項である
    if not self.HDate:
      self.setreply('550', '5.7.1', 'Breach of Header-Date Policy.')
      self.log_critical('550', 'Breach of Header-Date Policy.')
      ## ロギング用メールアドレスに記録を残さない場合、下記2行をコメントアウトする
      for rad in self.Rname:
        self.send_admin2(rad, u"ヘッダー日付エラー", "abort")
      return Milter.REJECT


    ### 差出人への返信不能
    ### 差出人は、複数登録できるが現在では一名を推奨している。
    ###   またメーラの基本操作では登録出来ない。(self.HFadER)
    if self.HFadER or (not self.HFfqdn) or self.HFmyd or \
       self.HFdynip or (self.HFipok[0] == None):
      self.setreply('550', '5.7.1',
                    '%s: Breach of Header-From Local Policy.' %
                    (','.join([m for d, m in self.FromAD])))
      self.log_critical('550', 'Breach of Header-From Local Policy.')
      ## ロギング用メールアドレスに記録を残さない場合、下記3行をコメントアウトする
      for rad in self.Rname:
        self.send_admin3(rad, u"差出人エラー", "abort")
      return Milter.REJECT

    ### 通常のメールサーバから送信されていない
    ### 再送要求上不可欠の要素である。また法令上の開示請求に対応できない。
    ### RFC上では、任意であるが早く必要事項としてフォーマットを含め定義するべきである。
    if not self.HMid:
      self.setreply('550', '5.7.1',
                    '%s: Breach of Message-ID Local Policy.' %
                    (','.join([m for d, m in self.FromAD])))
      self.log_critical('550', 'Breach of Message-ID Local Policy.')
      ## ロギング用メールアドレスに記録を残さない場合、下記3行をコメントアウトする
      for rad in self.Rname:
        self.send_admin3(rad, u"Message-IDエラー", "black")
      return Milter.REJECT

    ## ホワイトリストチェック
    ## チェックルールのテストロジック
    if whitech():
      self.PWmode = "white"
      for rad in self.Rname:
        self.send_admin2(rad, u"ホワイトリスト", "white")
      return Milter.CONTINUE

    ## 警告用ログと警告モードを残す。
    ## ML・NEWS等のルールが規格化されているのであれば、厳格に管理出きるのですが?
    ##   現状危険なメールを監視するように心がけています。
    ##   何らかの方法(DB等)で安心出来るheloホスト名等と送信者ドメインを管理出来れば警告を削減出来るのですが?
    if self.Cdynip or self.Hdynip or self.Hrelay or self.Relay or (not self.Hipok[0]) or \
       (self.Ffqdn and (not self.HFdnok) or (not self.HFadok)) or \
       (self.Fname == '<>'):
      self.PWmode = "gray"
      ## ロギング用メールアドレスに記録を残さない場合、下記をコメントアウトする
      msg = u"要注意:"
      if self.Cdynip or self.Hdynip:
        msg = msg + u"ダイナミックDNS "
      if not self.Hipok[0]:
        msg = msg + u"HELO "
      if self.Fname == '<>':
        msg = msg + u"Postmaster "
      if self.Ffqdn:
        if not self.HFdnok:
          msg = msg + u"送信者・差出人のドメイン違い "
        elif not self.HFadok:
          msg = msg + u"送信者・差出人のアドレス違い "
      if self.Hrelay or self.Relay:
        msg = msg + u"リレーサーバ "
      if self.HList:
        msg = msg + u"ML"
      for rad in self.Rname:
        ##self.send_admin2(rad, msg, "gray") # admin Only
        self.send_admin3(rad, msg, "gray") # admin or user

    return Milter.CONTINUE


  def eom(self):
##    self.log("eom")
    self.log_debug("eom")

    # ヘッダーにセンダーアドレスを追加する 迷惑メール対応をメーラーで行う為
    self.addheader('X-PWfrom', self.Fname)

    # ヘッダーにフィルターステイタスを追加する 迷惑メール対応をメーラーで行う為
    if self.PWmsg != "":
      self.addheader('X-PWmail', self.PWmsg)

    if self.PWmode != "":
      self.addheader('X-PWmode', self.PWmode)

    if self.PWmsg != "":
      self.log("X-PWmail:", self.PWmsg)
      self.PWmsg = ""

    if self.PWmode != "":
      self.log("X-PWmode:", self.PWmode)
      self.PWmode = ""

    return Milter.CONTINUE

  def abort(self):
    self.log_debug("abort")
    if self.Sabort == None:
      self.Sabort = True
    return Milter.CONTINUE

  def close(self):
    #
    # End Setup
    #
    # abort 時の注意点を指示する。
    if self.Sabort:
      self.log_warning("sever abort: mail server log read")

    if self.PWmsg != "":
      self.log("X-PWmail:", self.PWmsg)

    if self.PWmode != "":
      self.log("X-PWmode:", self.PWmode)

    self.log("close")
    return Milter.CONTINUE


  ## === Support Functions ===
  def log_debug(self, *msg):
    my_logger.debug('[%d] %s', self.id, ' '.join([str(m) for m in msg]))

  def log(self, *msg):
    my_logger.info('[%d] %s', self.id, ' '.join([str(m) for m in msg]))

  def log_warning(self, *msg):
    my_logger.warning('[%d] %s', self.id, ' '.join([str(m) for m in msg]))

  def log_error(self, *msg):
    my_logger.error('[%d] %s', self.id, ' '.join([str(m) for m in msg]))

  def log_critical(self, *msg):
    my_logger.critical('[%d] %s', self.id, ' '.join([str(m) for m in msg]))


## ===

def main():
  my_logger.info("pwfilter startup")

  global my_domainlist
  s = sys.argv[1]
  for v in s.split(','):
    p = (v, len(v))
    my_domainlist = my_domainlist + (p,)

  global my_hnameip
  my_hnameip = sys.argv[2]

  global to_pwadmin
  to_pwadmin = sys.argv[3]

  my_logger.info("mydomain:" + str(my_domainlist))
  dsnerror.load()
  dyndcheck.load()
  wlistcheck.load()

  # Register to have the Milter factory create instances of your class:
  Milter.factory = myMilter
  flags = Milter.ADDHDRS
  Milter.set_flags(flags)       # tell Sendmail which features we use

  Milter.runmilter("pwfilter", socketname, sockettimeout)

  dyndcheck.save()
  dsnerror.save()
  my_logger.info("pwfilter shutdown")

if __name__ == "__main__":
  main()