#!/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)