| 1 |
#! /usr/bin/python2.6 |
| 2 |
# -*- coding: utf-8 -*- |
| 3 |
|
| 4 |
# <pwfilter.py Receive e-mail filters And white list filter.> |
| 5 |
# Copyright (C) <2012> <yasuyosi kimura> |
| 6 |
# |
| 7 |
# This program is free software; you can redistribute it and/or modify |
| 8 |
# it under the terms of the GNU General Public License as published by |
| 9 |
# the Free Software Foundation; either version 3 of the License, |
| 10 |
# or (at your option) any later version. |
| 11 |
# |
| 12 |
# This program is distributed in the hope that it will be useful, |
| 13 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| 15 |
# See the GNU General Public License for more details. |
| 16 |
# |
| 17 |
# You should have received a copy of the GNU General Public License along |
| 18 |
# with this program. If not, see <http://www.gnu.org/licenses/>. |
| 19 |
|
| 20 |
### Revision 0.1.0 2012/03/03 18:00:00 Base version. |
| 21 |
### Change comment logic & add pwmode |
| 22 |
### Revision 0.0.2 2012/02/24 16:00:00 Test version. |
| 23 |
### Change comment logic parameter |
| 24 |
### Revision 0.0.1 2012/02/16 16:00:00 Test version. |
| 25 |
### Change DNSCheck |
| 26 |
### Revision 0.0.0 2012/01/13 14:00:00 Test version. |
| 27 |
### Vre 0.0.0 |
| 28 |
|
| 29 |
|
| 30 |
import sys |
| 31 |
import time |
| 32 |
import traceback |
| 33 |
import Milter |
| 34 |
from Milter.dynip import is_dynip as dynip |
| 35 |
from Milter.utils import parseaddr, parse_addr |
| 36 |
|
| 37 |
import re |
| 38 |
|
| 39 |
import smtplib |
| 40 |
import email |
| 41 |
from email.mime.text import MIMEText |
| 42 |
from email.header import Header, decode_header |
| 43 |
from email.utils import formatdate |
| 44 |
|
| 45 |
import logging |
| 46 |
import logging.handlers |
| 47 |
|
| 48 |
## ������������������������������������������������������ ## |
| 49 |
socketname = "inet:1025@localhost" |
| 50 |
sockettimeout = 600 |
| 51 |
|
| 52 |
ip4re = re.compile(r'^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$|' |
| 53 |
'^\[[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*\]$') |
| 54 |
hostre = re.compile(r'^[a-z0-9][-a-z0-9]*(\.[a-z0-9][-a-z0-9]*)*') |
| 55 |
fqdn = re.compile(r'^[a-z0-9][-a-z0-9]*' |
| 56 |
'(\.[a-z0-9][-a-z0-9]*)*(\.[a-z]{2,10})$') |
| 57 |
fqdnjp = re.compile(r'\.[a-z]{2,2}\.jp$|' |
| 58 |
'([a-z0-9][-a-z0-9]{2,63}\.[a-z]{2,10})$') |
| 59 |
|
| 60 |
## %(levelno)s Numeric logging level for the message (DEBUG, INFO, |
| 61 |
## WARNING, ERROR, CRITICAL) |
| 62 |
## ������������������������������������������������������ ## |
| 63 |
log_filename = "/var/log/pwmail/pwfilter.log" |
| 64 |
log_level = logging.INFO |
| 65 |
|
| 66 |
my_logger = logging.getLogger("pwfilter") |
| 67 |
my_logger.setLevel(log_level) |
| 68 |
log_fh = logging.handlers.RotatingFileHandler(log_filename, |
| 69 |
maxBytes=1024000, |
| 70 |
backupCount=10) |
| 71 |
log_fh.setLevel(logging.DEBUG) |
| 72 |
log_fm = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") |
| 73 |
log_fh.setFormatter(log_fm) |
| 74 |
my_logger.addHandler(log_fh) |
| 75 |
|
| 76 |
## (������������������������������������������) ## |
| 77 |
## ������������������������������ |
| 78 |
my_domainlist = () |
| 79 |
## ���������������������������������IP |
| 80 |
my_hnameip = "192.168.200.100" |
| 81 |
## ��������������������������������������������������������� |
| 82 |
to_pwadmin = "pwadmin@xxxx1.jp" |
| 83 |
|
| 84 |
## ������������������������������������������������������ ## |
| 85 |
## ��������������������������������� |
| 86 |
sendmail_port = 1026 |
| 87 |
## postfix recipient_delimiter ������������������������������������������������ |
| 88 |
recipient_delimiter = '+' |
| 89 |
|
| 90 |
|
| 91 |
### |
| 92 |
### dns ������������������ |
| 93 |
import DNS |
| 94 |
from DNS import DNSError |
| 95 |
MAX_CNAME = 10 |
| 96 |
|
| 97 |
## Lookup DNS records by label and RR type. |
| 98 |
# The response can include records of other types that the DNS |
| 99 |
# server thinks we might need. |
| 100 |
# @param name the DNS label to lookup |
| 101 |
# @param qtype the name of the DNS RR type to lookup |
| 102 |
# @return a list of ((name,type),data) tuples |
| 103 |
|
| 104 |
|
| 105 |
def DNSLookup(name, qtype): |
| 106 |
try: |
| 107 |
# To be thread safe, we create a fresh DnsRequest with |
| 108 |
# each call. It would be more efficient to reuse |
| 109 |
# a req object stored in a Session. |
| 110 |
req = DNS.DnsRequest(name, qtype=qtype) |
| 111 |
resp = req.req() |
| 112 |
#resp.show() |
| 113 |
# key k: ('wayforward.net', 'A'), value v |
| 114 |
# FIXME: pydns returns AAAA RR as 16 byte binary string, but |
| 115 |
# A RR as dotted quad. For consistency, this driver should |
| 116 |
# return both as binary string. |
| 117 |
if resp.header['tc'] == True: |
| 118 |
try: |
| 119 |
req = DNS.DnsRequest(name, qtype=qtype, protocol='tcp') |
| 120 |
resp = req.req() |
| 121 |
except IOError, x: |
| 122 |
raise DNSError('TCP Fallback error: ' + str(x)) |
| 123 |
return [((a['name'], a['typename']), a['data']) for a in resp.answers] |
| 124 |
except IOError, x: |
| 125 |
raise DNSError(str(x)) |
| 126 |
|
| 127 |
|
| 128 |
class DNSSession(object): |
| 129 |
"""A Session object has a simple cache with no TTL that is valid |
| 130 |
for a single "session", for example an SMTP conversation.""" |
| 131 |
|
| 132 |
def __init__(self): |
| 133 |
self.cache = {} |
| 134 |
|
| 135 |
## Additional DNS RRs we can safely cache. |
| 136 |
# We have to be careful which additional DNS RRs we cache. For |
| 137 |
# instance, PTR records are controlled by the connecting IP, and they |
| 138 |
# could poison our local cache with bogus A and MX records. |
| 139 |
# Each entry is a tuple of (query_type,rr_type). So for instance, |
| 140 |
# the entry ('MX','A') says it is safe (for milter purposes) to cache |
| 141 |
# any 'A' RRs found in an 'MX' query. |
| 142 |
SAFE2CACHE = frozenset(( |
| 143 |
('MX','MX'), ('MX','A'), |
| 144 |
('CNAME','CNAME'), ('CNAME','A'), |
| 145 |
('A','A'), ('AAAA','AAAA'), |
| 146 |
('PTR','PTR'), |
| 147 |
('NS','NS'), ('NS','A'), |
| 148 |
('TXT','TXT'), |
| 149 |
('SPF','SPF'))) |
| 150 |
|
| 151 |
## Cached DNS lookup. |
| 152 |
# @param name the DNS label to query |
| 153 |
# @param qtype the query type, e.g. 'A' |
| 154 |
# @param cnames tracks CNAMES already followed in recursive calls |
| 155 |
|
| 156 |
def dns(self, name, qtype, cnames=None): |
| 157 |
"""DNS query. |
| 158 |
|
| 159 |
If the result is in cache, return that. Otherwise pull the |
| 160 |
result from DNS, and cache ALL answers, so additional info |
| 161 |
is available for further queries later. |
| 162 |
|
| 163 |
CNAMEs are followed. |
| 164 |
|
| 165 |
If there is no data, [] is returned. |
| 166 |
|
| 167 |
pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF'] |
| 168 |
post: isinstance(__return__, types.ListType) |
| 169 |
""" |
| 170 |
result = self.cache.get((name, qtype)) |
| 171 |
cname = None |
| 172 |
|
| 173 |
if not result: |
| 174 |
safe2cache = DNSSession.SAFE2CACHE |
| 175 |
for k, v in DNSLookup(name, qtype): |
| 176 |
if k == (name, 'CNAME'): |
| 177 |
cname = v |
| 178 |
if (qtype, k[1]) in safe2cache: |
| 179 |
self.cache.setdefault(k, []).append(v) |
| 180 |
result = self.cache.get((name, qtype), []) |
| 181 |
if not result and cname: |
| 182 |
if not cnames: |
| 183 |
cnames = {} |
| 184 |
elif len(cnames) >= MAX_CNAME: |
| 185 |
#return result # if too many == NX_DOMAIN |
| 186 |
raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME) |
| 187 |
cnames[name] = cname |
| 188 |
if cname in cnames: |
| 189 |
raise DNSError('CNAME loop') |
| 190 |
result = self.dns(cname, qtype, cnames=cnames) |
| 191 |
return result |
| 192 |
|
| 193 |
DNS.DiscoverNameServers() |
| 194 |
|
| 195 |
###### |
| 196 |
import threading |
| 197 |
|
| 198 |
|
| 199 |
class HostCache: |
| 200 |
# ������������ |
| 201 |
# ������������������������ |
| 202 |
# ������������ |
| 203 |
# ������������������ |
| 204 |
|
| 205 |
def __init__(self, fname=None, maxcnt=7, maxrng=1000): |
| 206 |
self.maxcnt = maxcnt |
| 207 |
self.maxrng = maxrng |
| 208 |
self.rngcnt = 0 |
| 209 |
self.cache = {} |
| 210 |
self.fname = fname |
| 211 |
self.lock = threading.Lock() |
| 212 |
#with self.lock |
| 213 |
#self.lock.acquire() |
| 214 |
#try: |
| 215 |
# |
| 216 |
#finally: |
| 217 |
# self.lock.release() |
| 218 |
|
| 219 |
def hostcheck(self, hostname, dyn): |
| 220 |
with self.lock: |
| 221 |
if not self.cache.has_key(hostname): |
| 222 |
return None |
| 223 |
et, dc, ac = self.cache[hostname] |
| 224 |
now = time.time() |
| 225 |
if et > now: |
| 226 |
ac += 1 |
| 227 |
self.cache[hostname] = (et, dc, ac) |
| 228 |
return True |
| 229 |
if ac > 0: |
| 230 |
ac = 0 |
| 231 |
if (not dyn) and (dc <= self.maxcnt): |
| 232 |
dc += 1 |
| 233 |
et = now + dc*24*60*60 |
| 234 |
self.cache[hostname] = (et, dc, ac) |
| 235 |
return False |
| 236 |
|
| 237 |
def hostadd(self, hostname): |
| 238 |
with self.lock: |
| 239 |
if not self.cache.has_key(hostname): |
| 240 |
if self.rngcnt < self.maxrng: |
| 241 |
et = time.time() + 24*60*60 |
| 242 |
dc = 1 |
| 243 |
ac = 0 |
| 244 |
self.cache[hostname] = (et, dc, ac) |
| 245 |
self.rngcnt +=1 |
| 246 |
else: |
| 247 |
et, dc, ac = self.cache[hostname] |
| 248 |
ac += 1 |
| 249 |
self.cache[hostname] = (et, dc, ac) |
| 250 |
|
| 251 |
def hostdel(self, hostname): |
| 252 |
with self.lock: |
| 253 |
if self.cache.has_key(hostname): |
| 254 |
del self.cache[hostname] |
| 255 |
self.rngcnt -=1 |
| 256 |
|
| 257 |
|
| 258 |
def load(self, fname=None, maxcnt=0): |
| 259 |
if maxcnt > 0: |
| 260 |
self.maxcnt = maxcnt |
| 261 |
if fname: |
| 262 |
self.fname = fname |
| 263 |
self.rngcnt = 0 |
| 264 |
self.cache = {} |
| 265 |
try: |
| 266 |
with open(self.fname) as fp: |
| 267 |
for ln in fp: |
| 268 |
try: |
| 269 |
hostname, edtime, dcnt, acnt = ln.strip().split(',') |
| 270 |
et = float(edtime) |
| 271 |
dc = int(dcnt) |
| 272 |
ac = int(acnt) |
| 273 |
self.cache[hostname] = (et, dc, ac) |
| 274 |
self.rngcnt +=1 |
| 275 |
except: |
| 276 |
continue |
| 277 |
except: |
| 278 |
pass |
| 279 |
|
| 280 |
def save(self, fname=None, maxcnt=0): |
| 281 |
if maxcnt > 0: |
| 282 |
self.maxcnt = maxcnt |
| 283 |
if fname: |
| 284 |
self.fname = fname |
| 285 |
now = time.time() - self.maxcnt*24*60*60 |
| 286 |
try: |
| 287 |
with open(self.fname, "w") as fp: |
| 288 |
for hostname, v1 in self.cache.items(): |
| 289 |
try: |
| 290 |
et, dc, ac = v1 |
| 291 |
if (ac > 0) or ((ac == 0) and (et > now)): |
| 292 |
fp.write('%s,%f,%d,%d\n' % (hostname, et, dc, ac)) |
| 293 |
except: |
| 294 |
continue |
| 295 |
except: |
| 296 |
pass |
| 297 |
|
| 298 |
## ������������������������������������������������������ ## |
| 299 |
dsnerror = HostCache(fname="/var/log/pwmail/dsnerror.log", maxcnt=10) |
| 300 |
|
| 301 |
|
| 302 |
###������############### |
| 303 |
|
| 304 |
# Shift JIS UTF8 ������������unicode��������� |
| 305 |
def msg_cnvt(s): |
| 306 |
u = u"" |
| 307 |
for enc1 in ('utf8', 'cp932'): |
| 308 |
try: |
| 309 |
u = unicode(s, enc1) |
| 310 |
except UnicodeDecodeError: |
| 311 |
continue |
| 312 |
break |
| 313 |
if u == u"": |
| 314 |
u = unicode(s, 'utf8', 'replace') |
| 315 |
return u |
| 316 |
|
| 317 |
### |
| 318 |
### Milter.utils ������������������ |
| 319 |
def parse_header(val): |
| 320 |
"""Decode headers gratuitously encoded to hide the content. |
| 321 |
""" |
| 322 |
try: |
| 323 |
h = decode_header(val) |
| 324 |
if not len(h): |
| 325 |
return val |
| 326 |
u = [] |
| 327 |
for s, enc in h: |
| 328 |
if enc: |
| 329 |
try: |
| 330 |
u.append(unicode(s, enc)) |
| 331 |
except LookupError: |
| 332 |
u.append(unicode(s)) |
| 333 |
else: |
| 334 |
if isinstance(s, unicode): |
| 335 |
u.append(s) |
| 336 |
else: |
| 337 |
u.append(msg_cnvt(s)) |
| 338 |
u = ''.join(u) |
| 339 |
for enc in ('us-ascii', 'iso-8859-1', 'utf8'): |
| 340 |
try: |
| 341 |
return u.encode(enc) |
| 342 |
except UnicodeError: |
| 343 |
continue |
| 344 |
except UnicodeDecodeError: |
| 345 |
pass |
| 346 |
except LookupError: |
| 347 |
pass |
| 348 |
except email.Errors.HeaderParseError: |
| 349 |
pass |
| 350 |
return val |
| 351 |
|
| 352 |
# FQDN������������������������������ |
| 353 |
def fqdncheck(s): |
| 354 |
if fqdn.match(s): |
| 355 |
if s.find('-.') < 0: |
| 356 |
return True |
| 357 |
return False |
| 358 |
|
| 359 |
# ������������������������������������������ |
| 360 |
def my_domain_check(td): |
| 361 |
tl = len(td) |
| 362 |
for d, l in my_domainlist: |
| 363 |
if tl == l: |
| 364 |
if td == d: |
| 365 |
return True |
| 366 |
elif tl > l: |
| 367 |
if td.endswith(d): |
| 368 |
pw = td[tl - l -1] |
| 369 |
if (pw == '.') or (pw == '@'): |
| 370 |
return True |
| 371 |
return False |
| 372 |
|
| 373 |
# ��������������������������������������������� |
| 374 |
def eq_domain_check(td, d): |
| 375 |
tl = len(td) |
| 376 |
l = len(d) |
| 377 |
if tl == l: |
| 378 |
if td == d: |
| 379 |
return True |
| 380 |
elif tl > l: |
| 381 |
if td.endswith(d): |
| 382 |
if td[tl - l -1] == '.': |
| 383 |
return True |
| 384 |
return False |
| 385 |
|
| 386 |
|
| 387 |
|
| 388 |
|
| 389 |
class myMilter(Milter.Base): |
| 390 |
|
| 391 |
def __init__(self): # A new instance with each new connection. |
| 392 |
self.id = Milter.uniqueID() # Integer incremented with each call. |
| 393 |
|
| 394 |
# SMTP ��������������������������� |
| 395 |
def msg_connect(self, rcpt_addr): |
| 396 |
if not self.Cname: |
| 397 |
Cname = u"" |
| 398 |
else: |
| 399 |
Cname = msg_cnvt(self.Cname) |
| 400 |
if not self.IP: |
| 401 |
IP = u"" |
| 402 |
else: |
| 403 |
IP = msg_cnvt(self.IP) |
| 404 |
if not self.Hname: |
| 405 |
Hname = u"" |
| 406 |
else: |
| 407 |
Hname = msg_cnvt(self.Hname) |
| 408 |
if not self.Fname: |
| 409 |
Fname = u"" |
| 410 |
else: |
| 411 |
Fname = msg_cnvt(self.Fname) |
| 412 |
if not rcpt_addr: |
| 413 |
wrcpt_addr = u"" |
| 414 |
else: |
| 415 |
wrcpt_addr = msg_cnvt(rcpt_addr) |
| 416 |
try: |
| 417 |
return (u"connect from %s at %s\n" |
| 418 |
"helo: %s\nmail from: %s\n" |
| 419 |
"rcpt to: %s\n" % |
| 420 |
(Cname, IP, Hname, Fname, wrcpt_addr)) |
| 421 |
except: |
| 422 |
self.log_warning("msg_connect:", traceback.format_exc()) |
| 423 |
return u"" |
| 424 |
|
| 425 |
# SMTP ��������������������������� |
| 426 |
def msg_Header(self): |
| 427 |
if not self.HFrom: |
| 428 |
hfrom = u"" |
| 429 |
else: |
| 430 |
hfrom = msg_cnvt(self.HFrom) |
| 431 |
if not self.Subject: |
| 432 |
subject = u"" |
| 433 |
else: |
| 434 |
subject = msg_cnvt(self.Subject) |
| 435 |
if not self.HDate: |
| 436 |
hdate = u"" |
| 437 |
else: |
| 438 |
hdate = msg_cnvt(self.HDate) |
| 439 |
if not self.HMid: |
| 440 |
hmid = u"" |
| 441 |
else: |
| 442 |
hmid = msg_cnvt(self.HMid) |
| 443 |
try: |
| 444 |
return (u"Header-From: %s\n" |
| 445 |
"Header-Subject: %s\n" |
| 446 |
"Header-Date: %s\n" |
| 447 |
"Message-ID: %s\n" % (hfrom, subject, hdate, hmid)) |
| 448 |
except: |
| 449 |
self.log_warning("msg_Header:", traceback.format_exc()) |
| 450 |
return u"" |
| 451 |
|
| 452 |
|
| 453 |
|
| 454 |
# ��������������� ������������������SMTP ��������������������� |
| 455 |
def send_admin1(self, rcpt_addr, subject, ERlevel): |
| 456 |
encoding = "ISO-2022-JP" |
| 457 |
|
| 458 |
body = self.msg_connect(rcpt_addr) + u"X-PWmail: %s\n" % self.PWmsg |
| 459 |
mf = parse_addr(rcpt_addr) |
| 460 |
un = mf[0] |
| 461 |
pos = un.find('+') |
| 462 |
if pos != -1: |
| 463 |
un = un[:pos] |
| 464 |
from_addr = un + "+admin@" + mf[1] |
| 465 |
to_addr = un + "+" + ERlevel + "@" + mf[1] |
| 466 |
|
| 467 |
try: |
| 468 |
msg = MIMEText(body.encode(encoding, 'replace'), 'plain', encoding) |
| 469 |
msg['Subject'] = Header(subject, encoding) |
| 470 |
msg['From'] = from_addr |
| 471 |
msg['To'] = to_addr |
| 472 |
msg['Date'] = formatdate() |
| 473 |
except: |
| 474 |
self.log_warning('send_admin1 MIMEText:', traceback.format_exc()) |
| 475 |
return |
| 476 |
|
| 477 |
s = smtplib.SMTP('localhost', sendmail_port, 'localhost') |
| 478 |
try: |
| 479 |
s.sendmail(from_addr, [to_pwadmin], msg.as_string()) |
| 480 |
except: |
| 481 |
self.log_warning('send_admin1 sendmail:', traceback.format_exc()) |
| 482 |
s.quit() |
| 483 |
|
| 484 |
|
| 485 |
# ��������������� ��������������� |
| 486 |
#���SMTP ������������������������������������������ |
| 487 |
def send_admin2(self, rcpt_addr, subject, ERlevel): |
| 488 |
encoding = "ISO-2022-JP" |
| 489 |
|
| 490 |
body = self.msg_connect(rcpt_addr) + self.msg_Header() + \ |
| 491 |
u"X-PWmail: %s\n" % (self.PWmsg) |
| 492 |
mf = parse_addr(rcpt_addr) |
| 493 |
un = mf[0] |
| 494 |
pos = un.find('+') |
| 495 |
if pos != -1: |
| 496 |
un = un[:pos] |
| 497 |
from_addr = un + "+admin@" + mf[1] |
| 498 |
to_addr = un + "+" + ERlevel + "@" + mf[1] |
| 499 |
|
| 500 |
try: |
| 501 |
msg = MIMEText(body.encode(encoding, 'replace'), 'plain', encoding) |
| 502 |
msg['Subject'] = Header(subject, encoding) |
| 503 |
msg['From'] = from_addr |
| 504 |
msg['To'] = to_addr |
| 505 |
if not self.HDate: |
| 506 |
msg['Date'] = formatdate() |
| 507 |
else: |
| 508 |
msg['Date'] = self.HDate |
| 509 |
except: |
| 510 |
self.log_warning('send_admin2 MIMEText:', traceback.format_exc()) |
| 511 |
return |
| 512 |
|
| 513 |
s = smtplib.SMTP('localhost', sendmail_port, 'localhost') |
| 514 |
try: |
| 515 |
s.sendmail(from_addr, [to_pwadmin], msg.as_string()) |
| 516 |
except: |
| 517 |
self.log_warning('send_admin2 sendmail:', traceback.format_exc()) |
| 518 |
s.quit() |
| 519 |
|
| 520 |
# ��������������� ��������������������������� |
| 521 |
#���SMTP ������������������������������������������ |
| 522 |
def send_admin3(self, rcpt_addr, subject, ERlevel): |
| 523 |
encoding = "ISO-2022-JP" |
| 524 |
|
| 525 |
body = self.msg_connect(rcpt_addr) + self.msg_Header() + \ |
| 526 |
u"X-PWmail: %s\n" % (self.PWmsg) |
| 527 |
mf = parse_addr(rcpt_addr) |
| 528 |
un = mf[0] |
| 529 |
pos = un.find('+') |
| 530 |
if pos != -1: |
| 531 |
un = un[:pos] |
| 532 |
from_addr = un + "+admin@" + mf[1] |
| 533 |
to_addr = un + "+" + ERlevel + "@" + mf[1] |
| 534 |
|
| 535 |
try: |
| 536 |
msg = MIMEText(body.encode(encoding, 'replace'), 'plain', encoding) |
| 537 |
msg['Subject'] = Header(subject, encoding) |
| 538 |
msg['From'] = from_addr |
| 539 |
msg['To'] = to_addr |
| 540 |
if not self.HDate: |
| 541 |
msg['Date'] = formatdate() |
| 542 |
else: |
| 543 |
msg['Date'] = self.HDate |
| 544 |
except: |
| 545 |
self.log_warning('send_admin3 MIMEText:', traceback.format_exc()) |
| 546 |
return |
| 547 |
|
| 548 |
s = smtplib.SMTP('localhost', sendmail_port, 'localhost') |
| 549 |
try: |
| 550 |
s.sendmail(from_addr, [to_addr, to_pwadmin], msg.as_string()) |
| 551 |
except: |
| 552 |
self.log_warning('send_admin3 sendmail:', traceback.format_exc()) |
| 553 |
s.quit() |
| 554 |
|
| 555 |
|
| 556 |
###������????############### |
| 557 |
# name���ipad������������������������������������ |
| 558 |
# name������������������������������������������������������������������������ |
| 559 |
# name������������������������������MX��������������������������������������������� |
| 560 |
# DNS��������������������������� |
| 561 |
def DNSCheck(self, name, ipad): |
| 562 |
adok = False |
| 563 |
ad4ok = False |
| 564 |
adng = False |
| 565 |
|
| 566 |
mxok = False |
| 567 |
mx4ok = False |
| 568 |
mx6ok = False |
| 569 |
mxng = False |
| 570 |
|
| 571 |
rv = False |
| 572 |
rm = rt = '' |
| 573 |
|
| 574 |
s = DNSSession() |
| 575 |
try: |
| 576 |
ads = s.dns(name, 'A') |
| 577 |
if len(ads) > 0: |
| 578 |
ad4ok = True |
| 579 |
for a in ads: |
| 580 |
if a == ipad: |
| 581 |
adok = True |
| 582 |
elif not a: |
| 583 |
adng = True |
| 584 |
|
| 585 |
mxr = s.dns(name, 'MX') |
| 586 |
if len(mxr) > 0: |
| 587 |
for v, n in mxr: |
| 588 |
ads = s.dns(n, 'A') |
| 589 |
if len(ads) > 0: |
| 590 |
mx4ok = True |
| 591 |
for a in ads: |
| 592 |
if a == ipad: |
| 593 |
mxok = True |
| 594 |
elif not a: |
| 595 |
mxng = True |
| 596 |
else: |
| 597 |
ads = s.dns(n, 'AAAA') |
| 598 |
if len(ads) > 0: |
| 599 |
mx6ok = True |
| 600 |
for a in ads: |
| 601 |
if not a: |
| 602 |
mxng = True |
| 603 |
else: |
| 604 |
mxng = True |
| 605 |
|
| 606 |
if adng or mxng or ((not ad4ok) and (not mx4ok)): |
| 607 |
rv = None |
| 608 |
else: |
| 609 |
if adok: |
| 610 |
rv = True |
| 611 |
rm += 'A' |
| 612 |
if mxok: |
| 613 |
rv = True |
| 614 |
rm += 'M' |
| 615 |
|
| 616 |
if ad4ok: |
| 617 |
rt += 'A' |
| 618 |
if mx4ok: |
| 619 |
rt += 'M' |
| 620 |
if mx6ok: |
| 621 |
rt += '6' |
| 622 |
|
| 623 |
except: |
| 624 |
self.log_warning("DNS:", traceback.format_exc()) |
| 625 |
rv = None |
| 626 |
rm = rt = 'DNS' |
| 627 |
return (rv, rm, rt) |
| 628 |
|
| 629 |
|
| 630 |
def HostCheck(self, Hname, ipad, dyn): |
| 631 |
## ������������������������������ #################################### |
| 632 |
hcheck = dsnerror.hostcheck(Hname, dyn) |
| 633 |
if hcheck: |
| 634 |
ipok = (None, '', '') |
| 635 |
else: |
| 636 |
ipok = self.DNSCheck(Hname, ipad) |
| 637 |
if (ipok[0] == None): |
| 638 |
if (hcheck == None): |
| 639 |
dsnerror.hostadd(Hname) |
| 640 |
elif (hcheck == False): |
| 641 |
dsnerror.hostdel(Hname) |
| 642 |
return ipok |
| 643 |
|
| 644 |
|
| 645 |
# @Milter.noreply |
| 646 |
def connect(self, IPname, family, hostaddr): |
| 647 |
# ������������������ |
| 648 |
## self.IPname ��������� |
| 649 |
# self.Cname |
| 650 |
# self.Cfqdn |
| 651 |
# self.Cdynip |
| 652 |
# self.Cipok |
| 653 |
# self.Sabort |
| 654 |
# |
| 655 |
# REJECT��������������������������������������������������������� |
| 656 |
# 554 Transaction failed (Or, in the case of a connection-opening |
| 657 |
# response, "No SMTP service here") |
| 658 |
# X.3.5 System incorrectly configured |
| 659 |
|
| 660 |
self.log("connect from %s at %s" % (IPname, hostaddr)) |
| 661 |
# Ini Setup |
| 662 |
self.PWmsg = "" |
| 663 |
self.PWmode = "" |
| 664 |
self.Sabort = None |
| 665 |
|
| 666 |
if hostaddr and len(hostaddr) > 0: |
| 667 |
self.IP = hostaddr[0] |
| 668 |
else: |
| 669 |
self.log_critical("REJECT: connect attacks") |
| 670 |
self.setreply('554', '5.3.5', 'Banned for connect attacks') |
| 671 |
return Milter.REJECT |
| 672 |
|
| 673 |
self.IP = hostaddr[0] |
| 674 |
## self.port = hostaddr[1] |
| 675 |
self.Cname = IPname.lower() # Name from a reverse IP lookup |
| 676 |
if fqdncheck(self.Cname): |
| 677 |
self.Cfqdn = True |
| 678 |
self.Cdynip = dynip(self.Cname, self.IP) |
| 679 |
self.Cipok = self.HostCheck(IPname, self.IP, self.Cdynip) |
| 680 |
if (not self.Cipok[0]) and (self.Cname != IPname): |
| 681 |
self.Cipok = self.HostCheck(self.Cname, self.IP, self.Cdynip) |
| 682 |
else: |
| 683 |
self.Cfqdn = False |
| 684 |
self.Cipok = (None, '', '') |
| 685 |
self.Cdynip = None |
| 686 |
|
| 687 |
self.Hname = None |
| 688 |
self.Hmyd = None |
| 689 |
self.Hipok = (None, '', '') |
| 690 |
return Milter.CONTINUE |
| 691 |
|
| 692 |
|
| 693 |
# @Milter.noreply |
| 694 |
def hello(self, heloname): |
| 695 |
# ������������������ |
| 696 |
## self.heloname ��������� |
| 697 |
# self.Hname |
| 698 |
# self.Hfqdn |
| 699 |
# self.Hmyd |
| 700 |
# self.Hdynip |
| 701 |
# self.Hipok |
| 702 |
# |
| 703 |
# REJECT��������������������������������������������������������� |
| 704 |
# 504 Command parameter not implemented |
| 705 |
# X.5.1 Invalid command |
| 706 |
# X.5.1 ��������������������� |
| 707 |
# X.5.2 Syntax error |
| 708 |
# X.5.2 ��������������� |
| 709 |
|
| 710 |
hname = msg_cnvt(heloname) |
| 711 |
self.log("HELO", hname.encode('utf8', 'replace')) |
| 712 |
|
| 713 |
# Ini Setup |
| 714 |
self.PWmsg = "" |
| 715 |
self.PWmode = "" |
| 716 |
if self.Cfqdn: |
| 717 |
if self.Cdynip: |
| 718 |
self.PWmsg = self.PWmsg + "Cdynip " # yellow |
| 719 |
if self.Cipok[0] == None: |
| 720 |
self.PWmsg = self.PWmsg + "ngCipok " # error |
| 721 |
elif self.Cipok[0] == False: |
| 722 |
self.PWmsg = self.PWmsg + "noCipok " # error gray |
| 723 |
else: |
| 724 |
self.PWmsg = self.PWmsg + "noCfqdn " |
| 725 |
|
| 726 |
self.Hname = heloname.lower() |
| 727 |
self.Hmyd = None |
| 728 |
self.Hipok = (None, '', '') |
| 729 |
|
| 730 |
if not fqdncheck(self.Hname): |
| 731 |
# |
| 732 |
if ip4re.match(self.Hname): |
| 733 |
hnameip = self.Hname |
| 734 |
if hnameip[0] == '[': |
| 735 |
hnameip = hnameip[1:-1] |
| 736 |
if hnameip == my_hnameip: |
| 737 |
self.Hmyd = True |
| 738 |
self.PWmsg = self.PWmsg + "Hmyd " # error |
| 739 |
# ��������������������� ��������� REJECT ������������������������ |
| 740 |
elif not hostre.match(self.Hname): |
| 741 |
self.log_critical("REJECT: Helo command Syntax error") |
| 742 |
self.setreply('554', '5.5.2', |
| 743 |
'<%s>: Helo command Syntax error' % (heloname)) |
| 744 |
return Milter.REJECT |
| 745 |
# |
| 746 |
self.Hfqdn = False |
| 747 |
self.Hdynip = None |
| 748 |
self.PWmsg = self.PWmsg + "noHfqdn " # error gray |
| 749 |
return Milter.CONTINUE |
| 750 |
|
| 751 |
self.Hfqdn = True |
| 752 |
|
| 753 |
self.Hmyd = my_domain_check(self.Hname) |
| 754 |
if self.Hmyd: |
| 755 |
self.PWmsg = self.PWmsg + "Hmyd " # error |
| 756 |
return Milter.CONTINUE |
| 757 |
|
| 758 |
self.Hdynip = dynip(self.Hname, self.IP) |
| 759 |
if self.Hdynip: |
| 760 |
self.PWmsg = self.PWmsg + "Hdynip " # yellow |
| 761 |
|
| 762 |
if self.Cfqdn: |
| 763 |
if self.Cname == self.Hname: |
| 764 |
self.Hipok = self.Cipok |
| 765 |
if self.Hipok[0] == None: |
| 766 |
self.PWmsg = self.PWmsg + "ngHipok " # error |
| 767 |
elif self.Hipok[0] == False: |
| 768 |
self.PWmsg = self.PWmsg + "noHipok " # error gray |
| 769 |
return Milter.CONTINUE |
| 770 |
|
| 771 |
if self.Cipok[0]: |
| 772 |
if eq_domain_check(self.Cname, self.Hname): #type1 |
| 773 |
wHipok = self.HostCheck(self.Hname, self.IP, self.Hdynip) |
| 774 |
if wHipok[0]: |
| 775 |
self.Hipok = wHipok |
| 776 |
else: |
| 777 |
self.Hipok = (True, self.Cipok[1], wHipok[2]) |
| 778 |
return Milter.CONTINUE |
| 779 |
|
| 780 |
# ������������������������������������������������������������ |
| 781 |
Hdmain = self.Hname |
| 782 |
pos = Hdmain.find('.') |
| 783 |
Hdmain = Hdmain[pos + 1:] |
| 784 |
if fqdnjp.search(Hdmain): |
| 785 |
if eq_domain_check(self.Cname, Hdmain): #type2 |
| 786 |
wHipok = self.HostCheck(Hdmain, self.IP, self.Hdynip) |
| 787 |
if wHipok[0]: |
| 788 |
self.Hipok = wHipok |
| 789 |
else: |
| 790 |
self.Hipok = (True, self.Cipok[1], wHipok[2]) |
| 791 |
return Milter.CONTINUE |
| 792 |
|
| 793 |
self.Hipok = self.HostCheck(self.Hname, self.IP, self.Hdynip) |
| 794 |
# ������������������������������������������ |
| 795 |
if (not self.Hipok[0]): |
| 796 |
if (self.Hname != heloname): |
| 797 |
self.Hipok = self.HostCheck(heloname, self.IP, self.Hdynip) |
| 798 |
|
| 799 |
# ������������������������������������������ |
| 800 |
if (not self.Hipok[0]): |
| 801 |
Hdmain = self.Hname |
| 802 |
pos = Hdmain.find('.') |
| 803 |
Hdmain = Hdmain[pos + 1:] |
| 804 |
if fqdnjp.search(Hdmain): |
| 805 |
self.Hipok = self.HostCheck(Hdmain, self.IP, self.Hdynip) |
| 806 |
|
| 807 |
if self.Hipok[0] == None: |
| 808 |
self.PWmsg = self.PWmsg + "ngHipok " # error |
| 809 |
elif self.Hipok[0] == False: |
| 810 |
self.PWmsg = self.PWmsg + "noHipok " # error gray |
| 811 |
|
| 812 |
return Milter.CONTINUE |
| 813 |
|
| 814 |
|
| 815 |
# @Milter.noreply |
| 816 |
def envfrom(self, mailfrom, *str): |
| 817 |
# ������������������ |
| 818 |
# self.Fname |
| 819 |
# self.Fad |
| 820 |
# self.Fd |
| 821 |
# self.Ffqdn |
| 822 |
# self.Fmyd |
| 823 |
# self.Fdynip |
| 824 |
# self.Fipok |
| 825 |
# self.Relay |
| 826 |
# |
| 827 |
# REJECT��������������������������������������������������������� |
| 828 |
# 550 |
| 829 |
# 555 MAIL FROM/RCPT TO parameters not recognized or not implemented |
| 830 |
# 555 MAIL FROM/RCPT TO ��������������������������������������������������������������������������� |
| 831 |
# X.1.8 Bad sender's system address |
| 832 |
# X.1.8 ������������������������������������������������������ |
| 833 |
# X.4.3 Directory server failure |
| 834 |
# X.4.3 ������������������������������������ |
| 835 |
# X.4.4 Unable to route |
| 836 |
# X.4.4 ������������������������ |
| 837 |
|
| 838 |
self.log("mail from:", mailfrom, *str) |
| 839 |
|
| 840 |
# Ini Setup |
| 841 |
self.Fname = mailfrom |
| 842 |
self.Fad = '' |
| 843 |
self.Fd = '' |
| 844 |
self.Ffqdn = None |
| 845 |
self.Fmyd = False |
| 846 |
self.Fdynip = None |
| 847 |
self.Fipok = (None, '', '') |
| 848 |
self.Relay = False |
| 849 |
|
| 850 |
self.Rname = [] # list of recipients |
| 851 |
|
| 852 |
if self.Fname == '<>': |
| 853 |
return Milter.CONTINUE |
| 854 |
|
| 855 |
mb = parseaddr(self.Fname) |
| 856 |
if (mb[1] == None) or (mb[1] == ''): |
| 857 |
self.Ffqdn = False |
| 858 |
self.PWmsg = self.PWmsg + "noFfqdn " # error |
| 859 |
return Milter.CONTINUE |
| 860 |
|
| 861 |
self.Fad = mb[1] |
| 862 |
mf = parse_addr(mb[1]) |
| 863 |
if len(mf) != 2: |
| 864 |
self.Ffqdn = False |
| 865 |
self.PWmsg = self.PWmsg + "noFfqdn " # error |
| 866 |
return Milter.CONTINUE |
| 867 |
|
| 868 |
self.Fd = mf[1].lower() |
| 869 |
if not fqdncheck(self.Fd): |
| 870 |
self.Ffqdn = False |
| 871 |
self.PWmsg = self.PWmsg + "noFfqdn " # error |
| 872 |
return Milter.CONTINUE |
| 873 |
|
| 874 |
self.Ffqdn = True |
| 875 |
|
| 876 |
self.Fmyd = my_domain_check(self.Fd) |
| 877 |
if self.Fmyd: |
| 878 |
self.PWmsg = self.PWmsg + "Fmyd " # error |
| 879 |
return Milter.CONTINUE |
| 880 |
|
| 881 |
self.Fdynip = dynip(self.Fd, self.IP) |
| 882 |
if self.Fdynip: |
| 883 |
self.PWmsg = self.PWmsg + "Fdynip " # error |
| 884 |
|
| 885 |
self.Fipok = self.HostCheck(self.Fd, self.IP, self.Fdynip) |
| 886 |
if self.Fipok[0] == None: |
| 887 |
self.PWmsg = self.PWmsg + "ngFipok " # error not(MX/A) |
| 888 |
return Milter.CONTINUE |
| 889 |
|
| 890 |
# ������������������������������������������������������ |
| 891 |
if self.Fipok[0] == False: |
| 892 |
self.PWmsg = self.PWmsg + "noFipok " # OK |
| 893 |
if self.Hipok[0]: |
| 894 |
if eq_domain_check(self.Hname, self.Fd): #type1 |
| 895 |
return Milter.CONTINUE |
| 896 |
if self.Cipok[0]: |
| 897 |
if eq_domain_check(self.Cname, self.Fd): #type1 |
| 898 |
return Milter.CONTINUE |
| 899 |
|
| 900 |
Fdmain = self.Fd |
| 901 |
pos = Fdmain.find('.') |
| 902 |
Fdmain = Fdmain[pos + 1:] |
| 903 |
if fqdnjp.search(Fdmain): |
| 904 |
if self.Hipok[0]: |
| 905 |
if eq_domain_check(self.Hname, Fdmain): #type2 |
| 906 |
return Milter.CONTINUE |
| 907 |
if self.Cipok[0]: |
| 908 |
if eq_domain_check(self.Cname, Fdmain): #type2 |
| 909 |
return Milter.CONTINUE |
| 910 |
|
| 911 |
self.Relay = True |
| 912 |
self.PWmsg = self.PWmsg + "relay " # yellow |
| 913 |
|
| 914 |
return Milter.CONTINUE |
| 915 |
|
| 916 |
|
| 917 |
# @Milter.noreply |
| 918 |
def envrcpt(self, recipient, *str): |
| 919 |
# ������������������ |
| 920 |
# self.Rname |
| 921 |
# |
| 922 |
# REJECT��������������������������������������������������������� |
| 923 |
# 550 |
| 924 |
# 553 Requested action not taken: mailbox name not allowed |
| 925 |
# (e.g.,mailbox syntax incorrect) |
| 926 |
# X.1.1 Bad destination mailbox address |
| 927 |
# X.1.1 ��������������������������������������������������������� |
| 928 |
# X.7.1 Delivery not authorized, message refused |
| 929 |
# X.7.1 ��������������������������������������������������������������������� |
| 930 |
# X.2.2 Mailbox full |
| 931 |
# X.2.2 ������������������������������ |
| 932 |
# |
| 933 |
# 450 Requested mail action not taken: mailbox unavailable |
| 934 |
# (e.g.,mailbox busy or temporarily blocked for policy reasons) |
| 935 |
# 450 ������������������������������������������������(��������������������������������������������� |
| 936 |
# ���������������������������������������������������������������������������������)��������������� |
| 937 |
# ������������������������������������ |
| 938 |
# X.2.1 Mailbox disabled, not accepting messages |
| 939 |
# X.2.1 ������������������������������������������������������������������������������ |
| 940 |
# X.2.2 Mailbox full |
| 941 |
# X.2.2 ������������������������������ |
| 942 |
|
| 943 |
|
| 944 |
self.log("rcpt to:", recipient, ":", *str) |
| 945 |
self.Rname.append(recipient) |
| 946 |
|
| 947 |
### |
| 948 |
### ���������submission������������������������������������������ |
| 949 |
### |
| 950 |
### Abort ��������������� ������������������������������ |
| 951 |
### rcpt������������������REJECT���������������������������helo��������������������������������������������������������������� |
| 952 |
### |
| 953 |
### ��������������������������������������������������������������������� 550 ��� 450 ��������������������������� |
| 954 |
### ��������������������������������������������������������������������������������������� 550 ������������������������������������ |
| 955 |
|
| 956 |
|
| 957 |
## ��������������������������������������������������������������������� |
| 958 |
## ��������������������������������������������������� |
| 959 |
if recipient.lower() == to_pwadmin: |
| 960 |
self.setreply('550', '5.1.1', |
| 961 |
'<%s>: Recipient address rejected: User unknown.' % |
| 962 |
(recipient)) |
| 963 |
self.log_critical('550', 'Recipient address rejected: User unknown.') |
| 964 |
## ��������������������������������������������������������������������������������������������������������������� |
| 965 |
self.send_admin1(recipient, u"������������������������������������", "abort") |
| 966 |
return Milter.REJECT |
| 967 |
|
| 968 |
## ��������������������������������������������������� |
| 969 |
## ������������������������������������������������������(postfix recipient_delimiter = +)������������������������ |
| 970 |
## ��������������������������������������������������� |
| 971 |
if recipient.find(recipient_delimiter) != -1: |
| 972 |
self.setreply('550', '5.1.1', |
| 973 |
'<%s>: Recipient address rejected: User unknown.' % |
| 974 |
(recipient)) |
| 975 |
self.log_critical('550', 'Recipient address rejected: User unknown.') |
| 976 |
## ��������������������������������������������������������������������������������������������������������������� |
| 977 |
self.send_admin1(recipient, u"���������������������", "abort") |
| 978 |
return Milter.REJECT |
| 979 |
|
| 980 |
## Abort ��������������� |
| 981 |
## (rcpt������������������REJECT���������������������������������������������������������������) |
| 982 |
## ������������������������������������������������������������connect Helo ��������������������������������������������� |
| 983 |
## ������������������������������������������������������������������������������������������������������ |
| 984 |
if not self.Fipok[0]: |
| 985 |
## ������������Helo ��������������� FQDN ��������� |
| 986 |
if not self.Hfqdn: |
| 987 |
self.setreply('550', '5.5.2', |
| 988 |
'<%s>: Helo command rejected: ' |
| 989 |
'need fully-qualified hostname.' % (self.Hname)) |
| 990 |
self.log_error('550', 'Helo command rejected: need fully-qualified hostname.') |
| 991 |
## ��������������������������������������������������������������������������������������������������������������� |
| 992 |
#self.send_admin1(recipient, u"Helo���������������������������������FQDN���������", "abort0") |
| 993 |
return Milter.REJECT |
| 994 |
|
| 995 |
### Helo ������������������������������������������������������������������connectHost������������������������������ |
| 996 |
if not self.Hipok[0]: |
| 997 |
### ������������������������������������������������������������ |
| 998 |
if (self.Cfqdn) and (not self.Cipok[0]): |
| 999 |
self.setreply('550', '5.1.8', |
| 1000 |
'(%s:%s): connectHost rejected: DNS Host not found.' % |
| 1001 |
(self.Cname,self.IP)) |
| 1002 |
self.log_error('550', 'connectHost rejected: DNS Host not found.') |
| 1003 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1004 |
#self.send_admin1(recipient, u"������������������������������������������������������������", "abort0") |
| 1005 |
return Milter.REJECT |
| 1006 |
|
| 1007 |
### Helo ������������������������������������������������������������ |
| 1008 |
self.setreply('550', '5.1.8', |
| 1009 |
'(%s:%s): Helo command rejected: DNS Host not found.' % |
| 1010 |
(self.Hname,self.IP)) |
| 1011 |
self.log_error('550', 'Helo command rejected: DNS Host not found.') |
| 1012 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1013 |
#self.send_admin1(recipient, u"Helo ������������������������������������������������������������", "abort0") |
| 1014 |
return Milter.REJECT |
| 1015 |
|
| 1016 |
### Helo ��������������������������������������������������������� |
| 1017 |
### ���������submission��������������������������������������������������� |
| 1018 |
if self.Hmyd: |
| 1019 |
self.setreply('550', '5.5.2', |
| 1020 |
'<%s>: Helo command rejected: Breach of Local Policy.' % |
| 1021 |
(self.Hname)) |
| 1022 |
self.log_critical('550', |
| 1023 |
'Helo command rejected: Breach of Local Policy.') |
| 1024 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1025 |
self.send_admin1(recipient, u"���������������������������������������", "abort") |
| 1026 |
return Milter.REJECT |
| 1027 |
|
| 1028 |
if self.Fname != '<>': # (postmaster) OK |
| 1029 |
|
| 1030 |
## SenderHost ������FQDN���������������������������������������������DNS��������������������������������������������� |
| 1031 |
if (self.Ffqdn == False) or (self.Fdynip): |
| 1032 |
self.setreply('550', '5.5.2', |
| 1033 |
'%s: Sender address rejected: ' |
| 1034 |
'need fully-qualified address.' % (self.Fname)) |
| 1035 |
self.log_critical('550', |
| 1036 |
'Sender address rejected: ' |
| 1037 |
'need fully-qualified address.') |
| 1038 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1039 |
self.send_admin1(recipient, u"���������������������", "abort") |
| 1040 |
return Milter.REJECT |
| 1041 |
|
| 1042 |
### SenderHost������������������������������ |
| 1043 |
### ���������submission��������������������������������������������������� |
| 1044 |
if self.Fmyd: |
| 1045 |
self.setreply('550', '5.5.2', |
| 1046 |
'%s: Sender address rejected: Breach of Local Policy.' % |
| 1047 |
(self.Fname)) |
| 1048 |
self.log_critical('550', |
| 1049 |
'Sender address rejected: Breach of Local Policy.') |
| 1050 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1051 |
self.send_admin1(recipient, u"������������������������������", "abort") |
| 1052 |
return Milter.REJECT |
| 1053 |
|
| 1054 |
### SenderHost������������������������ |
| 1055 |
## ������������������������������������������������������������������ |
| 1056 |
## ������������������Message-ID ��������������������������������� |
| 1057 |
if self.Fipok[0] == None: |
| 1058 |
self.setreply('550', '5.4.3', |
| 1059 |
'%s: Sender address rejected: Breach of Domain.' % |
| 1060 |
(self.Fname)) |
| 1061 |
self.log_critical('550', |
| 1062 |
'Sender address rejected: Breach of Domain.') |
| 1063 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1064 |
self.send_admin1(recipient, u"���������������������������������", "abort") |
| 1065 |
return Milter.REJECT |
| 1066 |
|
| 1067 |
return Milter.CONTINUE |
| 1068 |
|
| 1069 |
|
| 1070 |
# @Milter.noreply |
| 1071 |
def data(self): |
| 1072 |
# |
| 1073 |
# REJECT��������������������������������������������������������� |
| 1074 |
# 550 rejections for policy reasons |
| 1075 |
# 450 rejections for policy reasons |
| 1076 |
# 550 450 ��������������������������������������� |
| 1077 |
# X.7.1 Delivery not authorized, message refused |
| 1078 |
# X.7.1 ��������������������������������������������������������������������� |
| 1079 |
# |
| 1080 |
# X.1.8 Bad sender's system address |
| 1081 |
# X.1.8 ������������������������������������������������������ |
| 1082 |
# X.4.3 Directory server failure |
| 1083 |
# X.4.3 ������������������������������������ |
| 1084 |
# X.4.4 Unable to route |
| 1085 |
# X.4.4 ������������������������ |
| 1086 |
# X.5.2 Syntax error |
| 1087 |
# X.5.2 ��������������� |
| 1088 |
|
| 1089 |
## self.log("data") |
| 1090 |
self.log_debug("data") |
| 1091 |
|
| 1092 |
# Ini Setup |
| 1093 |
self.FromAD = [] |
| 1094 |
self.HFrom = None |
| 1095 |
self.HDate = None |
| 1096 |
self.Subject = None |
| 1097 |
self.HMid = None |
| 1098 |
self.HList = None |
| 1099 |
|
| 1100 |
return Milter.CONTINUE |
| 1101 |
|
| 1102 |
|
| 1103 |
# @Milter.noreply |
| 1104 |
def header(self, name, hval): |
| 1105 |
### self.log_debug("header:%s: %s" % (name,hval)) |
| 1106 |
|
| 1107 |
nbuf = name.lower() |
| 1108 |
if nbuf == "from": |
| 1109 |
ms = [] |
| 1110 |
adbuf = hval.split(',') |
| 1111 |
for ad in adbuf: |
| 1112 |
ma = parseaddr(ad) |
| 1113 |
mn = parse_header(ma[0]) |
| 1114 |
ms.append(mn + ' <' + ma[1] + '>') |
| 1115 |
self.FromAD.append((mn, ma[1])) |
| 1116 |
mf = ",".join(ms) |
| 1117 |
if not self.HFrom: |
| 1118 |
self.HFrom = mf |
| 1119 |
else: |
| 1120 |
self.HFrom = self.HFrom + ',' + mf |
| 1121 |
self.log_debug("Header-From-B:", hval) |
| 1122 |
self.log("Header-From:", mf) |
| 1123 |
elif nbuf == "date": |
| 1124 |
self.HDate = hval |
| 1125 |
elif nbuf == "subject": |
| 1126 |
self.Subject = parse_header(hval) |
| 1127 |
self.log_debug("Subject-B:", hval) |
| 1128 |
self.log("Subject:", self.Subject) |
| 1129 |
elif nbuf == "message-id": |
| 1130 |
self.log("Message-ID:", hval) |
| 1131 |
self.HMid = hval |
| 1132 |
elif nbuf.startswith("list-"): |
| 1133 |
self.HList = True |
| 1134 |
|
| 1135 |
return Milter.CONTINUE |
| 1136 |
|
| 1137 |
|
| 1138 |
# @Milter.noreply |
| 1139 |
def eoh(self): |
| 1140 |
## self.log("eoh") |
| 1141 |
self.log_debug("eoh") |
| 1142 |
|
| 1143 |
if not self.HDate: |
| 1144 |
self.PWmsg = self.PWmsg + "noHDate " # error |
| 1145 |
if not self.HMid: |
| 1146 |
self.PWmsg = self.PWmsg + "noHMid " # yellow (self.Cdynip,self.Hdynip,self.Relay) True:Error |
| 1147 |
|
| 1148 |
# Ini Setup |
| 1149 |
self.HFromAD = None |
| 1150 |
self.HFfqdn = None |
| 1151 |
self.HFmyd = False |
| 1152 |
self.HFdynip = None |
| 1153 |
self.HFipok = (None, '', '') |
| 1154 |
self.HFdnok = None |
| 1155 |
self.HFadok = None |
| 1156 |
|
| 1157 |
if len(self.FromAD) != 1: |
| 1158 |
self.HFadER = True |
| 1159 |
self.PWmsg = self.PWmsg + "HFadER " |
| 1160 |
else: |
| 1161 |
self.HFadER = False |
| 1162 |
ad = self.FromAD[0][1] |
| 1163 |
self.HFromAD = ad |
| 1164 |
### ������������������������ |
| 1165 |
if (ad[0] == "'") and (ad[-1] == "'"): |
| 1166 |
ad = ad[1:-1] |
| 1167 |
### ������������������������ |
| 1168 |
if (ad == None) or (ad == ''): |
| 1169 |
self.HFfqdn = False |
| 1170 |
self.PWmsg = self.PWmsg + "noHFfqdn " |
| 1171 |
else: |
| 1172 |
mf = parse_addr(ad) |
| 1173 |
if len(mf) != 2: |
| 1174 |
self.HFfqdn = False |
| 1175 |
self.PWmsg = self.PWmsg + "noHFfqdn " |
| 1176 |
else: |
| 1177 |
dn = mf[1].lower() |
| 1178 |
if not fqdncheck(dn): |
| 1179 |
self.HFfqdn = False |
| 1180 |
self.PWmsg = self.PWmsg + "noHFfqdn " |
| 1181 |
else: |
| 1182 |
self.HFfqdn = True |
| 1183 |
self.HFmyd = my_domain_check(dn) |
| 1184 |
if self.HFmyd: |
| 1185 |
self.PWmsg = self.PWmsg + "HFmyd " |
| 1186 |
else: |
| 1187 |
self.HFdynip = dynip(dn, self.IP) |
| 1188 |
if self.HFdynip: |
| 1189 |
self.PWmsg = self.PWmsg + "HFdynip " |
| 1190 |
|
| 1191 |
self.HFipok = self.HostCheck(dn, self.IP, self.HFdynip) |
| 1192 |
if self.HFipok[0] == None: |
| 1193 |
self.PWmsg = self.PWmsg + "ngHFipok " |
| 1194 |
elif self.HFipok[0] == False: |
| 1195 |
self.PWmsg = self.PWmsg + "noHFipok " |
| 1196 |
|
| 1197 |
if self.Ffqdn: |
| 1198 |
if eq_domain_check(self.Fd, dn): #type1 |
| 1199 |
self.HFdnok = True |
| 1200 |
else: |
| 1201 |
pos = dn.find('.') |
| 1202 |
sdn = dn[pos + 1:] |
| 1203 |
if fqdnjp.search(sdn): |
| 1204 |
if eq_domain_check(self.Fd, sdn): #type2 |
| 1205 |
self.HFdnok = True |
| 1206 |
if not self.HFdnok: |
| 1207 |
self.HFdnok = False |
| 1208 |
self.PWmsg = self.PWmsg + "noHFdnok " |
| 1209 |
|
| 1210 |
if self.Fad == ad: |
| 1211 |
self.HFadok = True |
| 1212 |
else: |
| 1213 |
self.HFadok = False |
| 1214 |
self.PWmsg = self.PWmsg + "noHFadok " |
| 1215 |
|
| 1216 |
if self.HList: |
| 1217 |
self.PWmsg = self.PWmsg + "List " |
| 1218 |
|
| 1219 |
|
| 1220 |
### ��������������������������������� |
| 1221 |
if not self.HDate: |
| 1222 |
self.setreply('550', '5.7.1', 'Breach of Header-Date Policy.') |
| 1223 |
self.log_critical('550', 'Breach of Header-Date Policy.') |
| 1224 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1225 |
for rad in self.Rname: |
| 1226 |
self.send_admin2(rad, u"���������������������������", "abort") |
| 1227 |
return Milter.REJECT |
| 1228 |
|
| 1229 |
|
| 1230 |
### ��������������������������� |
| 1231 |
### ��������������������������������������������������������������������������������� |
| 1232 |
### ���������������������������������������������������������(self.HFadER) |
| 1233 |
if (self.HFadER) or (not self.HFfqdn) or (self.HFmyd) or \ |
| 1234 |
(self.HFdynip) or (self.HFipok[0] == None): |
| 1235 |
self.setreply('550', '5.7.1', |
| 1236 |
'%s: Breach of Header-From Local Policy.' % |
| 1237 |
(','.join([m for d, m in self.FromAD]))) |
| 1238 |
self.log_critical('550', 'Breach of Header-From Local Policy.') |
| 1239 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1240 |
for rad in self.Rname: |
| 1241 |
##self.send_admin2(rad, u"������������������", "abort") |
| 1242 |
self.send_admin3(rad, u"������������������", "abort") |
| 1243 |
return Milter.REJECT |
| 1244 |
|
| 1245 |
### ��������������������������������������������������������� |
| 1246 |
### ��������������������������������������������������������������������������������������������������� |
| 1247 |
### RFC������������������������������������������������������������������������������������������������������������������ |
| 1248 |
if not self.HMid: |
| 1249 |
self.setreply('550', '5.7.1', |
| 1250 |
'%s: Breach of Message-ID Local Policy.' % |
| 1251 |
(','.join([m for d, m in self.FromAD]))) |
| 1252 |
self.log_critical('550', 'Breach of Message-ID Local Policy.') |
| 1253 |
## ��������������������������������������������������������������������������������������������������������������� |
| 1254 |
for rad in self.Rname: |
| 1255 |
##self.send_admin2(rad, u"Message-ID���������", "black") |
| 1256 |
self.send_admin3(rad, u"Message-ID���������", "black") |
| 1257 |
return Milter.REJECT |
| 1258 |
|
| 1259 |
|
| 1260 |
## ��������������������������������������������� |
| 1261 |
## ������������������������������������������������������������������������������������������������������������������������ |
| 1262 |
## ������������������������������������������������������������������������ |
| 1263 |
## ���������������������������������������������������helo��������������������������������������������������������������������������������������������������� |
| 1264 |
if self.Cdynip or self.Hdynip or self.Relay or (not self.Hipok[0]) or \ |
| 1265 |
((self.Ffqdn) and (not self.HFdnok) or (not self.HFadok)) or \ |
| 1266 |
(self.Fname == '<>'): |
| 1267 |
self.PWmode = "gray" |
| 1268 |
## ��������������������������������������������������������������������������������������������������������� |
| 1269 |
msg = u"������������" |
| 1270 |
if (self.Cdynip) or (self.Hdynip): |
| 1271 |
msg = msg + u"������������������������������" |
| 1272 |
if (not self.Hipok[0]): |
| 1273 |
msg = msg + u"���������������" |
| 1274 |
if (self.Fname == '<>'): |
| 1275 |
msg = msg + u"Postmaster���" |
| 1276 |
if (self.Ffqdn): |
| 1277 |
if (not self.HFdnok): |
| 1278 |
msg = msg + u"���������������������������������������������" |
| 1279 |
elif (not self.HFadok): |
| 1280 |
msg = msg + u"���������������������������������������������" |
| 1281 |
if (self.Relay): |
| 1282 |
msg = msg + u"���������������������" |
| 1283 |
if (self.HList): |
| 1284 |
msg = msg + u"������" |
| 1285 |
for rad in self.Rname: |
| 1286 |
##self.send_admin2(rad, msg, "gray") # admin Only |
| 1287 |
self.send_admin3(rad, msg, "gray") # admin or user |
| 1288 |
|
| 1289 |
return Milter.CONTINUE |
| 1290 |
|
| 1291 |
|
| 1292 |
def eom(self): |
| 1293 |
## self.log("eom") |
| 1294 |
self.log_debug("eom") |
| 1295 |
|
| 1296 |
# ��������������������������������������������������������������������������������������������������������� |
| 1297 |
self.addheader('X-PWfrom', self.Fname) |
| 1298 |
|
| 1299 |
# ��������������������������������������������������������������������������������������������������������������� |
| 1300 |
if self.PWmsg != "": |
| 1301 |
self.addheader('X-PWmail', self.PWmsg) |
| 1302 |
|
| 1303 |
if self.PWmode != "": |
| 1304 |
self.addheader('X-PWmode', self.PWmode) |
| 1305 |
|
| 1306 |
if self.PWmsg != "": |
| 1307 |
self.log("X-PWmail:", self.PWmsg) |
| 1308 |
self.PWmsg = "" |
| 1309 |
|
| 1310 |
if self.PWmode != "": |
| 1311 |
self.log("X-PWmode:", self.PWmode) |
| 1312 |
self.PWmode = "" |
| 1313 |
|
| 1314 |
return Milter.CONTINUE |
| 1315 |
|
| 1316 |
def abort(self): |
| 1317 |
self.log_debug("abort") |
| 1318 |
self.Sabort = True |
| 1319 |
return Milter.CONTINUE |
| 1320 |
|
| 1321 |
def close(self): |
| 1322 |
# |
| 1323 |
# End Setup |
| 1324 |
# |
| 1325 |
# abort ��������������������������������� |
| 1326 |
if self.Sabort: |
| 1327 |
self.log_warning("sever abort: mail server log read") |
| 1328 |
|
| 1329 |
if self.PWmsg != "": |
| 1330 |
self.log("X-PWmail:", self.PWmsg) |
| 1331 |
|
| 1332 |
if self.PWmode != "": |
| 1333 |
self.log("X-PWmode:", self.PWmode) |
| 1334 |
|
| 1335 |
self.log("close") |
| 1336 |
return Milter.CONTINUE |
| 1337 |
|
| 1338 |
|
| 1339 |
## === Support Functions === |
| 1340 |
def log_debug(self, *msg): |
| 1341 |
my_logger.debug('[%d] %s', self.id, ' '.join([str(m) for m in msg])) |
| 1342 |
|
| 1343 |
def log(self, *msg): |
| 1344 |
my_logger.info('[%d] %s', self.id, ' '.join([str(m) for m in msg])) |
| 1345 |
|
| 1346 |
def log_warning(self, *msg): |
| 1347 |
my_logger.warning('[%d] %s', self.id, ' '.join([str(m) for m in msg])) |
| 1348 |
|
| 1349 |
def log_error(self, *msg): |
| 1350 |
my_logger.error('[%d] %s', self.id, ' '.join([str(m) for m in msg])) |
| 1351 |
|
| 1352 |
def log_critical(self, *msg): |
| 1353 |
my_logger.critical('[%d] %s', self.id, ' '.join([str(m) for m in msg])) |
| 1354 |
|
| 1355 |
|
| 1356 |
## === |
| 1357 |
|
| 1358 |
def main(): |
| 1359 |
my_logger.info("pwfilter startup") |
| 1360 |
|
| 1361 |
global my_domainlist |
| 1362 |
s = sys.argv[1] |
| 1363 |
for v in s.split(','): |
| 1364 |
p = (v, len(v)) |
| 1365 |
my_domainlist = my_domainlist + (p,) |
| 1366 |
|
| 1367 |
global my_hnameip |
| 1368 |
my_hnameip = sys.argv[2] |
| 1369 |
|
| 1370 |
global to_pwadmin |
| 1371 |
to_pwadmin = sys.argv[3] |
| 1372 |
|
| 1373 |
|
| 1374 |
my_logger.info("mydomain:" + str(my_domainlist)) |
| 1375 |
dsnerror.load() |
| 1376 |
|
| 1377 |
# Register to have the Milter factory create instances of your class: |
| 1378 |
Milter.factory = myMilter |
| 1379 |
flags = Milter.ADDHDRS |
| 1380 |
Milter.set_flags(flags) # tell Sendmail which features we use |
| 1381 |
|
| 1382 |
Milter.runmilter("pwfilter", socketname, sockettimeout) |
| 1383 |
|
| 1384 |
dsnerror.save() |
| 1385 |
my_logger.info("pwfilter shutdown") |
| 1386 |
|
| 1387 |
if __name__ == "__main__": |
| 1388 |
main() |