#! /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.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
from Milter.dns import Session as dnsSession

import re

import smtplib
from email.MIMEText import MIMEText
from email.Header import Header
from email.Utils import formatdate

import logging
import logging.handlers

socketname = "inet:1025@localhost"
sockettimeout = 600

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 = ()

sendmail_port = 1026  ## ロギング用メールポート
to_pwadmin = "pwadmin@xxxx1.jp"  ## ロギング用メールアドレス(全て小文字)

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:
          for enc1 in ('cp932','utf8'):
            try:
              u.append(unicode(s,enc1))
            except UnicodeDecodeError: continue
            break
    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


class myMilter(Milter.Base):

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


  def msg_cnvt(self, s):
    for enc1 in ('cp932','utf8'):
      try:
        u = unicode(s,enc1)
      except UnicodeDecodeError: continue
      break
    return u

  def msg_connect(self, rcpt_addr):
    if not self.Cname:
      Cname = u""
    else:
      Cname = self.msg_cnvt(self.Cname)
    if not self.IP:
      IP = u""
    else:
      IP = self.msg_cnvt(self.IP)
    if not self.Hname:
      Hname = u""
    else:
      Hname = self.msg_cnvt(self.Hname)
    if not self.Fname:
      Fname = u""
    else:
      Fname = self.msg_cnvt(self.Fname)
    if not rcpt_addr:
      wrcpt_addr = u""
    else:
      wrcpt_addr = self.msg_cnvt(rcpt_addr)
    try:
      return u"connect from %s at %s\nhelo: %s\nmail from: %s\nrcpt to: %s\n" % (Cname, IP, Hname, Fname, wrcpt_addr) 
    except:
      self.log_warning("msg_connect:", traceback.format_exc())
      return u""

  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

    # SMTPの引数を省略した場合はlocalhost:25
    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()

  def msg_Header(self, hd):
    if not self.HFrom:
      hfrom = u""
    else:
      hfrom = unicode(self.HFrom, 'utf8', 'replace')
    if not self.Subject:
      subject = u""
    else:
      subject = unicode(self.Subject, 'utf8', 'replace')
    try:
      return u"Header-From: %s\nHeader-Subject: %s\nHeader-Date: %s\n" % (hfrom, subject, hd)
    except:
      self.log_warning("msg_Header:", traceback.format_exc())
      return u""

  def send_admin2(self, rcpt_addr, subject, ERlevel):
    encoding = "ISO-2022-JP"
    if not self.HDate:
      hdate = ""
    else:
      hdate = self.HDate

    body = self.msg_connect(rcpt_addr) + self.msg_Header(hdate) + 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'] = hdate
    except:
      self.log_warning('send_admin2 MIMEText:',traceback.format_exc())
      return

    # SMTPの引数を省略した場合はlocalhost:25
    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()

  def send_admin3(self, rcpt_addr, subject, ERlevel):
    encoding = "ISO-2022-JP"
    if not self.HDate:
      hdate = ""
    else:
      hdate = self.HDate

    body = self.msg_connect(rcpt_addr) + self.msg_Header(hdate) + 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'] = hdate
    except:
      self.log_warning('send_admin3 MIMEText:',traceback.format_exc())
      return

    # SMTPの引数を省略した場合はlocalhost:25
    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()








#  @Milter.noreply
  def connect(self, IPname, family, hostaddr):
    #
    ## self.IPname
    # self.Cname
    # self.Cfqdn
    # self.Cdynip
    # self.Cipok
    # self.Sabort
    #
    self.log("connect from %s at %s" % (IPname, hostaddr) )

    # Ini Setup
    if hostaddr and len(hostaddr) > 0:
      self.IP = hostaddr[0]
    else: 
      self.log_critical("REJECT: connect attacks")
      self.setreply('550','5.7.1', '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 fqdn.match(self.Cname):
      self.Cfqdn = True
      self.Cdynip = dynip(self.Cname, self.IP)
      self.Cipok = self.DNSCheck(IPname, self.IP)
      if (not self.Cipok[0]) and (self.Cname != IPname):
        self.Cipok = self.DNSCheck(self.Cname, self.IP)
    else:
      self.Cfqdn = False
      self.Cipok = (None, '', '')
      self.Cdynip = None

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


#  @Milter.noreply
  def hello(self, heloname):
    #
    ## self.heloname
    # self.Hname
    # self.Hfqdn
    # self.Hmyd
    # self.Hdynip
    # self.Hipok
    #
    self.log("HELO",heloname)

    # Ini Setup
    self.PWmsg = ""
    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, '', '')

    if not fqdn.match(self.Hname):
      self.Hfqdn = False
      self.Hdynip = None
      self.PWmsg = self.PWmsg + "noHfqdn "              # error gray
      return Milter.CONTINUE

    self.Hfqdn = True

    self.Hmyd = self.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 self.Cfqdn:
      if self.Cname == self.Hname:
        self.Hipok = self.Cipok
        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.eq_domain_check(self.Cname, self.Hname): #type1
          wHipok = self.DNSCheck(self.Hname, self.IP)
          if wHipok[0]:
            self.Hipok = wHipok
          else:
            self.Hipok = (True, self.Cipok[1], wHipok[2])
          return Milter.CONTINUE

        # 同一ドメイン内のリレー及び記述ミスの回避
        Hdmain = self.Hname
        pos = Hdmain.find('.')
        Hdmain = Hdmain[pos+1:]
        if fqdnjp.search(Hdmain):
          if self.eq_domain_check(self.Cname, Hdmain): #type2
            wHipok = self.DNSCheck(Hdmain, self.IP)
            if wHipok[0]:
              self.Hipok = wHipok
            else:
              self.Hipok = (True, self.Cipok[1], wHipok[2])
            return Milter.CONTINUE

    self.Hipok = self.DNSCheck(self.Hname, self.IP)
    # 送信ホスト名のDNSの記述ミスの回避(ダイナミックIPも含む)
    if (not self.Hipok[0]) and (self.Hname != heloname):
      self.Hipok = self.DNSCheck(heloname, self.IP)

    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


#  @Milter.noreply
  def envfrom(self, mailfrom, *str):
    self.log("mail from:", mailfrom, *str)

    # Ini Setup
    self.Fname = mailfrom
    self.Fad = ''
    self.Fd = ''
    self.Ffqdn = None
    self.Fmyd = False
    self.Fdynip = None
    self.Fipok = (None, '', '')
    self.Relay = False
    
    self.Rname = []  # list of recipients

    if self.Fname == '<>':
      return Milter.CONTINUE

    mb = parseaddr(self.Fname)
    if (mb[1] == None) or (mb[1] == ''):
      self.Ffqdn = False
      self.PWmsg = self.PWmsg + "noFfqdn "              # error
      return Milter.CONTINUE

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

    self.Fd = mf[1].lower()
    if not fqdn.match(self.Fd):
      self.Ffqdn = False
      self.PWmsg = self.PWmsg + "noFfqdn "              # error
      return Milter.CONTINUE

    self.Ffqdn = True

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

    self.Fdynip = dynip(self.Fd, self.IP)
    if self.Fdynip:
      self.PWmsg = self.PWmsg + "Fdynip "             # error

    self.Fipok = self.DNSCheck(self.Fd, self.IP)
    if self.Fipok[0] == None:
      self.PWmsg = self.PWmsg + "ngFipok "            # error not(MX/A)
      return Milter.CONTINUE

    if self.Fipok[0] == False:
      self.PWmsg = self.PWmsg + "noFipok "            # OK
      if self.Hipok[0]:
        if self.eq_domain_check(self.Hname, self.Fd): #type1
          return Milter.CONTINUE
      if self.Cipok[0]:
        if self.eq_domain_check(self.Cname, self.Fd): #type1
          return Milter.CONTINUE

      Fdmain = self.Fd
      pos = Fdmain.find('.')
      Fdmain = Fdmain[pos+1:]
      if fqdnjp.search(Fdmain):
        if self.Hipok[0]:
          if self.eq_domain_check(self.Hname, Fdmain): #type2
            return Milter.CONTINUE
        if self.Cipok[0]:
          if self.eq_domain_check(self.Cname, Fdmain): #type2
            return Milter.CONTINUE

      self.Relay = True
      self.PWmsg = self.PWmsg + "relay "              # yellow

    return Milter.CONTINUE


#  @Milter.noreply
  def envrcpt(self, recipient, *str):
    self.log("rcpt to:", recipient, ":", *str)
    self.Rname.append(recipient)

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

    return Milter.CONTINUE


#  @Milter.noreply
  def data(self):
##    self.log("data")
    self.log_debug("data")

    ## Abort 条件の追加      
    ###++++++++++++++++++++Error
    if (not self.Hfqdn) and (not self.Cipok[0]) and (not self.Fipok[0]):
      self.setreply('504','5.5.2','<%s>: Helo command rejected: need fully-qualified hostname.' % (self.Hname))
      self.log_critical('504','(%s:%s) %s %s: Helo command rejected: need fully-qualified hostname.' % (self.Hname,self.IP,self.Fname,self.Rname))
      return Milter.REJECT

    ###++++++++++++++++++++Error
    if (not self.Cipok[0]) and (not self.Hipok[0]) and (not self.Fipok[0]):
      self.setreply('550','5.7.1','(%s:%s): Helo command rejected: Host not found.' % (self.Hname,self.IP))
      self.log_critical('550','(%s:%s) %s %s: Helo command rejected: Host not found.' % (self.Hname,self.IP,self.Fname,self.Rname))
      return Milter.REJECT

    #######################Abort
    if self.Hmyd:
      self.setreply('504','5.5.2','<%s>: Helo command rejected: Breach of Local Policy.' % (self.Hname))
      self.log_critical('504','(%s:%s) %s %s: Helo command rejected: Breach of Local Policy.' % (self.Hname,self.IP,self.Fname,self.Rname))
      ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
      ##for rad in self.Rname:
      ##  self.send_admin1(rad, u"送信ホスト名がマイドメイン", "abort")
      return Milter.REJECT


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

      #######################Abort
      if (self.Ffqdn == False) or (self.Fdynip):
        self.setreply('504','5.5.2','%s: Sender address rejected: need fully-qualified address.' % (self.Fname))
        self.log_critical('504','(%s:%s) %s %s: Sender address rejected: need fully-qualified address.' % (self.Hname,self.IP,self.Fname,self.Rname))
        ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
        ##for rad in self.Rname:
        ##  self.send_admin1(rad, u"送信者名エラー", "abort")
        return Milter.REJECT

      #######################Abort
      if self.Fmyd:
        self.setreply('504','5.5.2','%s: Sender address rejected: Breach of Local Policy.' % (self.Fname))
        self.log_critical('504','(%s:%s) %s %s: Sender address rejected: Breach of Local Policy.' % (self.Hname,self.IP,self.Fname,self.Rname))
        ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
        ##for rad in self.Rname:
        ##  self.send_admin1(rad, u"送信者がマイドメイン", "abort")
        return Milter.REJECT

      ##  リトライで真意の確認をする必要性があるのか?
      ##  その場合は、Message-ID の確認が必要と思われる
      #######################Abort    self.Fname != '<>'
      if self.Fipok[0] == None:
        self.setreply('550','5.7.1','%s: Sender address rejected: Breach of Domain.' % (self.Fname))
        self.log_critical('550','(%s:%s) %s %s: Sender address rejected: Breach of Domain.' % (self.Hname,self.IP,self.Fname,self.Rname))
        ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
        ##for rad in self.Rname:
        ##  self.send_admin1(rad, u"送信者ドメインエラー", "abort")
        return Milter.REJECT

    # 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):
##    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 fqdn.match(dn):
            self.HFfqdn = False
            self.PWmsg = self.PWmsg + "noHFfqdn "
          else:
            self.HFfqdn = True
            self.HFmyd = self.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.DNSCheck(dn, self.IP)
              if self.HFipok[0] == None:
                self.PWmsg = self.PWmsg + "ngHFipok "
              elif self.HFipok[0] == False:
                self.PWmsg = self.PWmsg + "noHFipok "
              if self.Ffqdn:
                if self.Fd == dn:
                  self.HFdnok = True
                else:
                  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 "


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

    ###--------------------Abort
    # 下位ロジックに纏める。
    #if not self.HFfqdn:
    #  self.setreply('550','5.7.1','%s: Header-From address rejected: need fully-qualified address.' % (self.HFromAD))
    #  self.log_critical('550','%s: Header-From address rejected: need fully-qualified address.' % (self.HFromAD))
    #  ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
    #  ##for rad in self.Rname:
    #  ##  self.send_admin2(rad, u"差出人名エラー", "abort")
    #  return Milter.REJECT

    ###--------------------Abort
    if (self.HFadER) or (not self.HFfqdn) or (self.HFmyd) or (self.HFdynip):
      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','(%s): Breach of Header-From Local Policy.' % (','.join([m for d, m in self.FromAD])))
      ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
      ##for rad in self.Rname:
      ##  self.send_admin2(rad, u"差出人エラー", "abort")
      return Milter.REJECT

    ###--------------------Abort
    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','(%s): Breach of Message-ID Local Policy.' % (','.join([m for d, m in self.FromAD])))
      ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
      ##for rad in self.Rname:
      ##  self.send_admin2(rad, u"Message-IDエラー", "abort")
      return Milter.REJECT

    ## ダイナミックDNSの場合の処理   black
    ###--------------------Abort
    if (self.Cdynip) and ((self.Hipok[0] == None) or (not self.Hfqdn)):
      self.setreply('550','5.7.1','(%s:%s): Helo command rejected: Host not found.' % (self.Hname,self.IP))
      self.log_critical('550','(%s:%s) %s %s: Helo command rejected: Host not found.' % (self.Hname,self.IP,self.Fname,self.Rname))
      ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
      ##for rad in self.Rname:
      ##  self.send_admin2(rad, u"ダイナミックDNSエラー", "abort") # admin Only
      ##  self.send_admin3(rad, u"ダイナミックDNSエラー", "abort") # admin or user
      return Milter.REJECT

    ## 警告用ログを残す。
    ## ロギング用メールアドレスに記録を残す場合、コメントアウトする
    ##if (self.Cdynip) or (self.Hdynip) or ((self.Relay) and (self.HList)):
    ##  msg = u"要注意:"
    ##  if (self.Cdynip) or (self.Hdynip):
    ##    msg = msg + u"ダイナミックDNS "
    ##  if (self.Relay) and (self.HList):
    ##    msg = msg + u"MLリレーサーバ "
    ##  for rad in self.Rname:
    ##    self.send_admin2(rad, msg, "black") # admin Only
    ##    self.send_admin3(rad, msg, "black") # 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)

    return Milter.CONTINUE

  def abort(self):
    self.log_debug("abort")
    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)

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


  ## === Support Functions ===

  def my_domain_check(self,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(self, 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 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:
        a4ok = 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 a4ok) and (not mx4ok)):
        rv = None
      else:
        if adok:
          rv = True
          rm += 'A'
        if mxok:
          rv = True
          rm += 'M'

      if a4ok:
        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 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,)

  my_logger.info("mydomain:" + str(my_domainlist))

  # 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)
  my_logger.info("pwfilter shutdown")

if __name__ == "__main__":
  main()