#!/usr/local/bin/python --
"""
This is a program by Dru Nelson. You can contact me
and discuss bugs/issues/kudos at dnelson@redwoodsoft.com.
This program acts as a qmail-queue filter.
If qmail-smtpd receives a piece of email, it normally
sends it to qmail-queue. With a small patch, it can
now send it to this program which will then call
qmail-queue.
20000513 - DN - Making the initial version: ILOVEYOU
20040621 - liu shiwei V1.3
"""
import os, sys, string, time, traceback, re
Version = '1.3'
PyVersion = '1.0'
Logging = 1
Debug = 0
QmailD = 501
LogFile = None
TestMode = None
Filters = []
MailEnvelope = ''
MailData = ''
MaxData = 2000000
MinData = 0
MailLen = 0
ValidActions = {'trap': '', 'drop' : '', 'block' :'','back' :'' }
def openlog():
global LogFile
if not Logging:
return
try:
LogFile = os.open('//var/qmail/qmfilt/qm.log', os.O_APPEND|os.O_WRONLY|os.O_CREAT, 0744)
except:
if TestMode:
print "Can't create /var/log/qmfilt"
else:
# Indicate a write error
sys.exit(53)
def log(message):
message = time.asctime(time.localtime(time.time())) + " - " + message + "\n"
if Logging:
os.write(LogFile, message)
# Indicate a write error
#sys.exit(53)
if TestMode:
print message
def showFilters():
if len(Filters) == 0:
print "No filters"
for f in Filters:
print "Filter - %s - Action: %s Regex: %s" % (f[0], f[1], f[2])
def readFilters():
global Filters
try:
file = open('/var/qmail/control/qmfilt', 'r')
configLines = file.readlines()
file.close()
except:
log("Can't read /var/qmail/control/qmfilt")
if not TestMode:
# Indicate a 'cannot read config file error'
sys.exit(53)
reg = re.compile('(.*)::(.+)::(.+)::(.*)::(.*)::')
for line in configLines:
if line[0] == '#':
continue
m = reg.match(line)
if m != None:
action = string.lower(m.group(2))
if not ValidActions.has_key(action):
log("Invalid action in config file [%s] - SKIPPED" %(action))
continue
Filters.append( [m.group(1), string.lower(m.group(2)), m.group(3),m.group(4),m.group(5)] )
def readDescriptor(desc):
global MailLen
result = ''
while 1:
data = os.read(desc, 16384)
if (data == ''):
break
result = result + data
return result
def writeDescriptor(desc, data):
while data:
num = os.write(desc, data)
data = data[num:]
os.close(desc)
def filterHits():
global MailLen
MailLen=len(MailData)
for regEx in Filters:
if Debug==1:
log("size-%d,%s:%s,%s" % (MailLen,regEx[0],regEx[3],regEx[4]))
if (len(regEx[3])>0) and (MailLen < int(regEx[3])):
if Debug==1:
log("skip %d<%s" % (MailLen,regEx[3]))
continue
if (len(regEx[4])>0) and (MailLen > int(regEx[4])):
if Debug==1:
log("skip %d>%s" % (MailLen,regEx[4]))
continue
if Debug==1:
log("run! %s-%s" %( regEx[0],regEx[2]));
reg = re.compile(regEx[2], re.M|re.I)
match = reg.search(MailData)
if match:
return regEx[1], regEx[0]
return None,None
def storeInTrap(hit):
try:
pid = os.getpid()
mailFile = os.open('/var/qmail/qmfilt/qmfilt.%s' %(pid), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
except:
log("Can't create /var/qmail/qmfilt/qmfilt.%s" %(pid))
# Indicate a write error
sys.exit(53)
try:
(size, inode, size, size, size,size, size, size, size, size) = os.fstat(mailFile)
os.rename('/var/qmail/qmfilt/qmfilt.%s' %(pid), '/var/qmail/qmfilt/%s.mail' %(inode))
except:
log("Can't rename /var/qmail/qmfilt/qmfilt.%s -> /var/qmail/qmfilt/%s.mail" %(pid, inode))
# Indicate a write error
sys.exit(53)
try:
envFile = os.open('/var/qmail/qmfilt/%s.envelope' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
mesgFile = os.open('/var/qmail/qmfilt/%s.qmfilt' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
writeDescriptor(mailFile, MailData)
writeDescriptor(envFile, MailEnvelope)
writeDescriptor(mesgFile, "Matched filter [ %s] to file %s" %(hit, inode))
log("Matched filter [ %s] to file %s" %(hit, inode))
except:
log("Can't create/write files into trap")
# Indicate a write error
sys.exit(53)
return 0
def sendToQmailQueue():
# Open a pipe to qmail queue
fd0 = os.pipe()
fd1 = os.pipe()
pid = os.fork()
if pid < 0:
log("Error couldn't fork")
sys.exit(81)
if pid == 0:
# This is the child
os.dup2(fd0[0], 0)
os.dup2(fd1[0], 1)
i = 2
while (i < 64):
try:
os.close(i)
except:
pass
i = i + 1
os.chdir('/var/qmail')
#time.sleep(10)
os.execv("bin/qmail-queue", ('bin/qmail-queue',))
log("Something went wrong")
sys.exit(127) # This is only reached on error
else:
# This is the parent
# close the readable descriptors
os.close(fd0[0])
os.close(fd1[0])
# Send the data
recvHdr = "Received: (anhengmail: V%s); " %(Version)
recvHdr = recvHdr + time.strftime("%d %b %Y %H:%M:%S", time.gmtime(time.time()))
recvHdr = recvHdr + " -0000\n"
os.write(fd0[1], recvHdr)
writeDescriptor(fd0[1], MailData)
writeDescriptor(fd1[1], MailEnvelope)
# Catch the exit code to return
result = os.waitpid(pid, 0)[1]
if PyVersion > '1.5.1':
if os.WIFEXITED(result):
return os.WEXITSTATUS(result)
else:
log("Didn't exit normally")
sys.exit(81)
else:
return result
def main(argv, stdout, environ):
global TestMode
global MailData
global MailEnvelope
global PyVersion
global MaxData
global MinData
PyVersion = string.split(sys.version)[0]
if len(argv) > 1 and argv[1] == '--test':
TestMode = 1
os.setuid(QmailD)
# Insure Environment is OK
# Try to open log
openlog()
if not os.path.exists('/var/qmail/qmfilt'):
# Indicate a problem with the queue directory
log("Directory /var/qmail/qmfilt doesn't exist")
if not TestMode:
sys.exit(61)
# #Python 1.5.2 feature
# if not os.access('/var/qmail/qmfilt', os.W_OK):
# # Indicate a problem with the queue directory
# log("Directory /var/qmail/qmfilt doesn't have write permission")
# if not TestMode:
# sys.exit(61)
if os.path.exists('/var/qmail/control/qmfilt'):
readFilters()
else:
if TestMode:
print "No filter file /var/qmail/control/qmfilt - no filters applied"
if TestMode:
showFilters()
# Go no further if in test mode
if TestMode:
sys.exit(0)
# Get the data
# Read the data
MailData = readDescriptor(0)
MailLen = len(MailData)
# Read the envelope
MailEnvelope = readDescriptor(1)
if (MailLen==0):
sys.exit(0)
if (MailLen>MaxData or MinData>MailLen):
log(MailEnvelope+"-GO-size:%d" % MailLen)
sendToQmailQueue()
sys.exit(0)
if Debug==9:
log(MailData)
log(MailEnvelope+"-size:%d" % MailLen)
action,hit = filterHits()
if action == 'trap':
storeInTrap(hit)
if action == 'block':
log("Matched filter [ %s] and email was BLOCKED/Refused delivery" %(hit))
sys.exit(31)
if action == 'back':
sendToQmailQueue()
storeInTrap(hit)
if action == 'drop':
log("Matched filter [ %s] and email was DROPPED" %(hit))
if action == None:
sendToQmailQueue()
if Debug==9:
log("qmailqueue returned [%d]" %(result))
sys.exit(0)
if __name__ == "__main__":
try:
main(sys.argv, sys.stdout, os.environ)
# Catch the sys.exit() errors
except SystemExit, val:
sys.exit(val)
except:
# return a fatal error for the unknown error
if TestMode:
traceback.print_exc()
sys.exit(81)