#! /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 ã«ä¿®æ£ã—ã¦ä¸‹ã•ã„。 ### ã“ã®å ´åˆã«ã¯ã€ä½•ã‚‰ã‹ã®æ–¹æ³•ï¼ˆï¼¤ï¼¢ç‰ï¼‰ã§æ¤œè¨¼ã—ã¦ï¼’回目ã‹ã‚‰ã¯ 550 ã§å¯¾å¿œã™ã‚‹äº‹ã‚’進ã‚ã¾ã™ã€‚ ## Abort æ¡ä»¶ã®è¿½åŠ ## é€ä¿¡è€…ドメインç‰ã§ï¼©ï¼°ç¢ºå®šã™ã‚‹å ´åˆã«ã¯ã€connect Helo コマンドã§ã®ãƒã‚§ãƒƒã‚¯ã¯ã—ãªã„。 ## 「マルãƒãƒ‰ãƒ¡ã‚¤ãƒ³ã‚µãƒ¼ãƒã«å¯¾ã™ã‚‹å¯¾å¿œã¨åˆå¿ƒè€…ã®è¨å®šãƒŸã‚¹ã«å¯¾ã—ã¦ã®å¯¾å¿œã€ if not self.Fipok[0]: ### é€ä¿¡ã‚µãƒ¼ãƒãŒãƒ›ã‚¹ãƒˆåç‰ã§ï¼©ï¼°ç¢ºå®šå‡ºæ¥ãªã„ ### ç¾åœ¨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.') # ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ # #self.send_admin0(u"é€ä¿¡ã‚µãƒ¼ãƒãŒãƒ›ã‚¹ãƒˆåç‰ã§ï¼©ï¼°ç¢ºå®šå‡ºæ¥ãªã„", "abort0") # #self.send_admin1(recipient, u"é€ä¿¡ã‚µãƒ¼ãƒãŒãƒ›ã‚¹ãƒˆåç‰ã§ï¼©ï¼°ç¢ºå®šå‡ºæ¥ãªã„", "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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ #self.send_admin0(u"Heloコマンドã®ãƒ›ã‚¹ãƒˆåã¯ã€FQDNã§ãªã„", "abort0") #self.send_admin1(recipient, u"Heloコマンドã®ãƒ›ã‚¹ãƒˆåã¯ã€FQDNã§ãªã„", "abort0") self.Sabort = False return Milter.REJECT ### Helo コマンドã®ãƒ‰ãƒ¡ã‚¤ãƒ³åç‰ã§ï¼©ï¼°ç¢ºå®šã™ã‚‹å ´åˆã¯ã€connectHostã®ãƒã‚§ãƒƒã‚¯ã¯ã—ãªã„。 if (self.Hipok[0] == None) or (self.Hipok[0] == False) and (not self.Cipok[0]): ### Helo コマンドã®ãƒ‰ãƒ¡ã‚¤ãƒ³åç‰ã§ï¼©ï¼°ç¢ºå®šå‡ºæ¥ãªã„ 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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ #self.send_admin0(u"Helo コマンドã®ãƒ‰ãƒ¡ã‚¤ãƒ³åç‰ã§ï¼©ï¼°ç¢ºå®šå‡ºæ¥ãªã„", "abort0") #self.send_admin1(recipient, u"Helo コマンドã®ãƒ‰ãƒ¡ã‚¤ãƒ³åç‰ã§ï¼©ï¼°ç¢ºå®šå‡ºæ¥ãªã„", "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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ 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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ 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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ self.send_admin0(u"é€ä¿¡è€…åãŒï¼¤ï¼®ï¼³ã‚¨ãƒ©ãƒ¼", "abort") #self.send_admin1(recipient, u"é€ä¿¡è€…åãŒï¼¤ï¼®ï¼³ã‚¨ãƒ©ãƒ¼", "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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ 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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ 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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼‘行をコメントアウトã™ã‚‹ 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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼’行をコメントアウトã™ã‚‹ 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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼“行をコメントアウトã™ã‚‹ 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.') ## ãƒã‚®ãƒ³ã‚°ç”¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«è¨˜éŒ²ã‚’残ã•ãªã„å ´åˆã€ä¸‹è¨˜ï¼“行をコメントアウトã™ã‚‹ 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 ## è¦å‘Šç”¨ãƒã‚°ã¨è¦å‘Šãƒ¢ãƒ¼ãƒ‰ã‚’残ã™ã€‚ ## ï¼ï¼¬ãƒ»ï¼®ï¼¥ï¼·ï¼³ç‰ã®ãƒ«ãƒ¼ãƒ«ãŒè¦æ ¼åŒ–ã•ã‚Œã¦ã„ã‚‹ã®ã§ã‚ã‚Œã°ã€åŽ³æ ¼ã«ç®¡ç†å‡ºãã‚‹ã®ã§ã™ãŒï¼Ÿ ## ç¾çŠ¶å±é™ºãªãƒ¡ãƒ¼ãƒ«ã‚’監視ã™ã‚‹ã‚ˆã†ã«å¿ƒãŒã‘ã¦ã„ã¾ã™ã€‚ ## 何らã‹ã®æ–¹æ³•ï¼ˆï¼¤ï¼¢ç‰ï¼‰ã§å®‰å¿ƒå‡ºæ¥ã‚‹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"ï¼ï¼¬" 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()