pycmail-0.1.4/0000755000175000017500000000000011020271020014155 5ustar garabikgarabik00000000000000pycmail-0.1.4/pycmail0000755000175000017500000002553011020267536015567 0ustar garabikgarabik00000000000000#! /usr/bin/python # Written by Radovan Garabik . # For new versions, look at http://kassiopeia.juls.savba..sk/~garabik/software/pycmail.html import sys, os, os.path, pwd, string, StringIO, re, time, traceback import pprint, getopt import rfc822, socket def dotlock(file, sleeptime=8, retries=-1, locktimeout=None, suspend=16): args = "-%i -r %i -s %i" % (sleeptime, retries, suspend) if locktimeout: args = args + " -l %i" % locktimeout os.system(lockfilebin+" "+args+" "+file+".lock") def dotunlock(file): os.unlink(file+".lock") def dotlocked(file): return os.path.isfile(file+".lock") class MailDestiny: def __init__(self, destinyname=""): self.destinyname = destinyname def name(self): return "generic mail destiny" class DevNull(MailDestiny): "send mail to /dev/null" def __init__(self): pass def getfd(self): self.fd = open("/dev/null", "a") return self.fd def closefd(self): self.fd.close() def name(self): return "/dev/null" class Pipe(MailDestiny): "send mail to another program - headers included" def getfd(self): self.fd = os.popen(self.destinyname, "w") return self.fd def closefd(self): self.fd.close() def name(self): return "Pipe to "+self.destinyname class Forward(MailDestiny): "forward mail to another address" def getfd(self): self.fd = os.popen(sendmailbin+" -i "+self.destinyname, "w") return self.fd def closefd(self): self.fd.close() def name(self): return "Forward to "+self.destinyname class MailDir(MailDestiny): "deliver mail to a maildir directory" def filename(self): return `long(time.time())`[:-1]+"."+`os.getpid()`+"."+socket.gethostname() def getfd(self): self.fname = self.filename() timer = 0 while os.path.isfile(self.destinyname+"/tmp"+self.fname): time.sleep(2) timer = timer + 1 if timer > 43200: # 24h # this should not happen Bounce("Unable to deliver.") return None try: os.makedirs(self.destinyname+"/tmp", 0700) os.makedirs(self.destinyname+"/cur", 0700) os.makedirs(self.destinyname+"/new", 0700) except: pass os.umask(077) self.fd=open(self.destinyname+"/tmp/"+self.fname,"a") return self.fd def closefd(self): self.fd.close() os.link(self.destinyname+"/tmp/"+self.fname, self.destinyname+"/new/"+self.fname) os.unlink(self.destinyname+"/tmp/"+self.fname) def name(self): return "MailDir "+self.destinyname class MailBox(MailDestiny): "deliver mail to BSD mail folder - needs lockfile (from procmail package) for locking to work!" def lock(self): if self.destinyname == defaultbox: os.system(lockfilebin+" -l 600 -ml") else: dotlock(self.destinyname, locktimeout=600) def unlock(self): if self.destinyname == defaultbox: os.system(lockfilebin+" -mu") else: dotunlock(self.destinyname) def locked(self): return dotlocked(self.destinyname) def filename(self): return self.destinyname def getfd(self): self.lock() self.fname = self.filename() os.umask(077) self.fd=open(self.fname,"a") return self.fd def closefd(self): self.fd.close() self.unlock() def name(self): return "MailBox "+self.destinyname def Debug(text="Debugging...", level=2): "print text if debuglevel >= level" if DEBUGLEVEL >=level: print 'DEBUG:', text def Append(*dest): "add to the mail destination" global DESTINATION for i in dest: DESTINATION.append(i) def Set(*dest): "set the mail destination" global DESTINATION DESTINATION = [] for i in dest: DESTINATION.append(i) def Junk(): Set(DevNull()) def SetDefault(): Set(default) def SendMail(recipient, From=None, sender=None, subject=None, text="pycmail test mail"): "send mail" # to do: specify eventual additional headers as parameters if DEBUGLEVEL >= 2: Debug("sending mail to %s, Subject: %s" % (recipient, subject), 2) return if From == None: From = USERNAME if sender == None: sender = USERNAME #sm = os.popen(sendmailbin+" -t", "w") sm = os.popen(sendmailbin+" -t -r "+sender, "w") sm.write("From: %s\n" % From) sm.write("To: %s\n" % recipient) if subject: sm.write("Subject: %s\n" % subject) sm.write("\n") sm.write(text) sm.close() def Reply(recipient=None, From=None, sender=None, subject=None, text="pycmail test reply"): "reply to the current mail" # to do: specify eventual additional headers as parameters if subject == None: subject = "Re: "+SUBJECT if recipient == None: recipient = mailmsg.getaddr('Reply-To:')[1] or ADDR_FROM[1] if recipient <> None: SendMail(recipient, From=From, sender=sender, subject=subject, text=text) def Bounce(text="Mail bounced."): "bounce mail. This only prints the text to stdout, which causes MTA to bounce the message" Debug("Bouncing mail.", 2) print text class PycmailStopException(Exception): "Exception raised and catched to signal end of script execution" pass def Stop(): raise PycmailStopException def Contains(s, sub, case=None, rex=0, flags=None): """return true if sub occurs in s if rex == 1, do it as regular expression, else just substring if searching substrings, default case=0, if regexps, case=1""" if rex: if case == None: case = 1 if flags == None: flags = re.M else: flags = flags+re.M if case: f = flags else: f = flags+re.I return re.search(sub, s or "", f) else: if case == None: case = 0 if not s: return False if case: return sub in s return sub.lower() in s.lower() def InHeader(hname, sub, case=None, rex=0, flags=None): "return true if sub is in header with name hname" return Contains(mailmsg.getheader(hname), sub, case, rex, flags) def untuple(list): "change list of tuples into tuple of lists" return map(lambda x: x[0], list), map(lambda x: x[1], list) # start of program is here USERNAME = pwd.getpwuid(os.getuid())[0] USERHOME = pwd.getpwuid(os.getuid())[5] DEBUGLEVEL = 0 TESTING = 0 try: optlist, args = getopt.getopt(sys.argv[1:], "td:c:D:", ["debuglevel=", "config="]) except: optlist, args = [], [] if not args: args = [sys.stdin] user_pycmailrc = USERHOME+"/.pycmailrc" DEFINES=[] for i in optlist: if i[0] in ['-d', '--debuglevel']: DEBUGLEVEL = string.atoi(i[1]) if i[0] in ['-t', '--test']: TESTING = 1 if i[0] in ['-c', '--config']: user_pycmailrc = os.path.expanduser(i[1]) if i[0] == "-D": DEFINES.append(i[1]) defaultbox = "/var/spool/mail/"+USERNAME default = MailBox(defaultbox) bufsize = 4096 bodysize = 1000 sendmailbin = "/usr/sbin/sendmail" lockfilebin = "/usr/bin/lockfile" SetDefault() # we need to set it here and below too, just in case there # is an error in /etc/pycmailrc if os.path.isfile("/etc/pycmailrc"): execfile("/etc/pycmailrc") SetDefault() for infile in args: try: if infile <> sys.stdin: infileds = open(infile) else: infileds = infile msg = "" while 1: l = infileds.readline() msg = msg+l if l == '\n' or l == "": message_read = 0 if l == "": # message contains only headers - a bit patological case, # but we handle it anyway BODY = "" msg = msg + '\n' # so that we do not corrupt next message message_read = 1 break if not message_read: to_read = bufsize*(1+(len(msg)+bodysize)/bufsize)-len(msg) BODY = infileds.read(to_read) msg = msg + BODY msgIO = StringIO.StringIO(msg) mailmsg = rfc822.Message(msgIO) FROM = mailmsg.getheader('From') or "" TO = mailmsg.getheader('To') or "" CC = mailmsg.getheader('Cc') or "" SUBJECT = mailmsg.getheader('Subject') or "" ADDR_FROM = mailmsg.getaddr('From') ADDR_TO = mailmsg.getaddr('To') ADDR_CC = mailmsg.getaddr('Cc') NAMELIST_TO, ADDRLIST_TO = untuple(mailmsg.getaddrlist('To')) NAMELIST_CC, ADDRLIST_CC = untuple(mailmsg.getaddrlist('Cc')) msgIO.close() Debug("From: %s\nTo %s\nCC: %s\nSubject: %s" % (FROM, TO, CC, SUBJECT), 4) Debug("Message headers:\n"+pprint.pformat(mailmsg.headers), 6) Debug("Message body:\n"+BODY, 8) if os.path.isfile(user_pycmailrc): try: execfile(user_pycmailrc) except PycmailStopException: # used to simulate a goto command pass except: # there is some error in .pycmailrc.... SetDefault() # delivery to system default mailbox if DEBUGLEVEL == 0: pass elif DEBUGLEVEL >= 1: traceback.print_exc() # I wonder if this is needed - user could have set DESTINATION to # an empty list deliberately, as a faster variant to DevNull(), # but OTOH, he might have screwed it up. # for the time being, we assume he screwed it up and and fall back to # default if DESTINATION == []: SetDefault() destfd = [] for i in DESTINATION: Debug("destination: "+i.name(), 2) if not TESTING: fd = i.getfd() if fd: Debug("writing headers+beginning of body", 4) fd.write(msg) destfd.append(fd) if not message_read: while 1: msg = infileds.read(bufsize) if msg == "": break Debug("writing next chunk", 4) Debug("next chunk of body: \n%s\n" % msg, 10) if not TESTING: for i in destfd: i.write(msg) finally: infileds.close() for i in DESTINATION: try: if not TESTING: i.closefd() except: if DEBUGLEVEL == 0: pass elif DEBUGLEVEL >= 1: traceback.print_exc() Debug("end of run", 4) pycmail-0.1.4/README0000644000175000017500000001023310515171554015057 0ustar garabikgarabik00000000000000Written by Radovan Garabík For new versions, look at http://melkor.dnp.fmph.uniba.sk/~garabik/pycmail.html WARNING: this program fiddles with your mail. You can lose your mail. Locking of mailboxes has been notoriously known for not being twice reliable. Especially for mailboxes on NFS. You can use maildir instead, it does not have locking problems. But you can lose your mail anyway. On top of this, pycmail is in active development. It can have bugs. You have been warned. Do not blame me if you lose your mail. A bit of terminology: MTA - mail transport agent. Sometimes called mail daemon, the program responsible for sending, receiving and delivering mail. Some well-known MTAs are: sendmail, exim, qmail, postfix, smail, zmailer pycmail should be invoked automatically from the .forward file when mail arrives. put following into your .forward: |/usr/bin/pycmail When invoked, it reads the beginning of mail message from stdin, and then it executes a file named $HOME/.pycmailrc. This file is a regular python program, for further description see README.python if you can program in python, and README.nopython if you can't. According to the commands in this file, the mail message that just arrived gets distributed into the right folder, gets forwarded, discarded, or piped to an external program. If no .pycmailrc is found, or processing of the .pycmailrc falls off the end, pycmail will store the mail in the default system mailbox. Requirements: pycmail can deliver mails either to maildir (http://cr.yp.to/proto/maildir.html), or ordinary BSD mailboxes. For maildirs, no other programs are required (apart from mail user agent capable of working with maildirs, such as mutt). For safe locking of BSD-style mailboxes pycmail uses lockfile(1) from the procmail package. You should install procmail first. For performance reason, the message is _not_ read whole into the memory (unlike procmail), rather it is read in chunks, and the chunks are immediately delivered. That means you have access only to the first chunk in .pycmailrc. You can modify the size of the chunks, and the size of message body read after headers in /etc/pycmailrc. Default of 1000 bytes is fine, longer messages are usually junk anyway, and you can find out if they are junk out of their beginning, there is no need to suck 10 MB mp3 file in the memory before processing. This has a small disadvantage: if you are delivering mail to multiple destinations, and one of them blocks (say, you are delivering to a mailbox over nfs and the network goes down), all the other destinations are blocked until the blocked chunk is completed (or timeouts). Currently pycmail recognizes these command line switches: -d debuglevel -t -c conffile the name of alterantive user configuration file (instead of ~/.pycmailrc) Any other argument is taken as the file name to read mail from, instead of standard input. You can specify more files, each of them has to contain one message. (TODO: make these files mailboxes and loop over messages) Default debuglevel is 0, which means pycmail tries to deliver mail in an almost foolproof way: if there is any error in ~/.pycmailrc file, mail will be delivered to the default mailbox (such as /var/spool/mail/USERNAME). But you will have hard time finding out where exactly is the error, if it does not deliver mail where you want. If the debuglevel is 1, any error in ~/.pycmailrc file will be printed, resulting in bounced mail (but pycmail will still try to deliver the mail to default mailbox). The bigger the debuglevel, the more detailed information is printed. Levels equal or higher than 2 are not suitable for actual usage, because they generate a lot of output, which results in bounced mails. You can use it if you do e.g. postprocessing of messages, or testing your configuration. If the -t option is used, pycmail will just print what would it do with the mail, the mail will be then discarded and no actual delivery will be done. Use this for testing purposes. To test your configuration, do something like this: formail -s pycmail -d 2 -t < your_test_mailbox_file pycmail-0.1.4/README.python0000644000175000017500000001216710515166367016415 0ustar garabikgarabik00000000000000This README is for you if you already speak python. If not, look at README.nopython This text is very brief and not yet finished. The best bet would be to look at the source code :-) .pycmailrc is execfile'd during the execution of pycmail.py. It is a regular python program, so you have all the power of python at your disposition. Following variables are predefined: FROM - string taken from From: header, with leading and trailing whitespace stripped TO - string taken from TO: header, with leading and trailing whitespace stripped CC - string taken from Cc: header, with leading and trailing whitespace stripped SUBJECT - string taken from Subject: header, with leading and trailing whitespace stripped ADDR_FROM - tuple of (name, address) taken from From: header e.g. if the header has form From: Guido van Rossum , ADDR_FROM would be ('Guido van Rossum', 'guido@python.org') ADDR_TO - tuple of (name, address) taken from To: header if there are more To: addresses, only the first is taken. ADDR_CC - tuple of (name, address) taken from Cc: header if there are more Cc: addresses, only the first is taken. NAMELIST_TO - list of names found in To: header ADDRLIST_TO - list of addresses found in To: header NAMELIST_CC - list of names found in Cc: header ADDRLIST_CC - list of addresses found in Cc: header BODY - string containing message body (or the beginning of it, if the message is long) msg - string containing the message, including headers and body (or just the beginning, if the message is too long) mailmsg - instance of rfc822 object, containing the message. Refer to rfc822 module documentation for more info USERNAME - name of user USERHOME - $HOME of user DEBUGLEVEL - debugging level (specified by -d switch) Following functions are predefined, you can use them in .pycmailrc file def Contains(sub, s, case=1, rex=0, flags=None): return true if string s is in string sub if rex=1, do regular expression maching, else just look for substring. if case=0, be case insensitive, else be case sensitive (default 0 for substrings, 1 for regular expressions) flags are flags for regular expression. example: if Contains(FROM, "spammer.*?@yahoo.com", case=0, rex=1, flags=re.S): some action def InHeader(hname, sub, case=1, rex=0, flags=None): return true if sub is in header with name hname it is a shorthand for Contains(mailmsg.getheader(hname), sub, case, rex, flags) example: if InHeader("X-Mailing-list", "debian-devel"): some action def Stop(): stops processing .pycmailrc. Alternative to using if ... elif... elif... elif... else Your .pycmailrc should set up DESTINATION list. It is a list of destinations your mail would be delivered into. It can contain only one destination (typically a mailbox), but it still has to be a list. If your .pycmailrc leaves it to be an empty list, the default destination (usually /var/spool/mail/username) is used. Valid destinations are: DevNull() no comment Pipe("name") os.popen program "name" and pass the message (with full headers) to it as stdin. if "name" prints anything to stdout, it is taken as error in mail delivery - use Pipe("name >/dev/null 2>&1") if it is undesirable. Forward("name@computer") forward mail to name@computer MailDir("directory") deliver mail to "directory", in maildir format MailBox("mailbox") deliver mail to "mailbox" in BSD mailbox format You can use following functions: Append(dest) this appends destination dest to existing DESTINATION Set(dest) this sets DESTINATION = [dest] Both Append() and Set() support multiple arguments: Append(dest1, dest2, dest3) Junk() is equivalent to DESTINATION = [DevNull()] SetDefault() sets default destination (usually /var/spool/mail/username) It is automatically set before processing .pycmailrc SendMail(recipient, From="from@mail.address", sender="sender@mail.address", subject="subject", text="pycmail test mail"): send a mail to recipient, if From is specified, the mail will look like it was sent from From address. (It is not intended for faking mails, but as an effective countermeasure against spam - MTA usually places your true identity in headers anyway). Ok, there are situations where you might want to modify the headers so that you true identity is concealed. In that case, use sender="address" to modify Sender: header. You probably must be in "trusted users" group to be able to do this, it depends on your MTA (this is how it is in exim). Reply(recipient="where@to.reply", From="from@mail.address", sender="who@sent.it", subject="subject", text="pycmail test reply"): reply to the current mail. Unless you specify explicitly recipient, the return address is taken from Reply-To: header, if it does not exist, from From: header. If not specified, subject is "Re: "+ original subject Bounce(text): prints text to stdout, which will cause the MTA to bounce the message Debug(text, level=2): if debuglevel is equal or greater than level, print text. Usually, when receiving mail, this means the mail will bounce with error described by text. pycmail-0.1.4/pycmail.10000644000175000017500000000333110515173175015721 0ustar garabikgarabik00000000000000.TH pycmail 1 "2006-10-17" .SH NAME pycmail \- mail sorter .SH SYNOPSIS .B pycmail .I "[-d debuglevel] [-t] [-c conffile] [file1 file2 file3 ...]" .SH "DESCRIPTION" .B pycmail is a mail sorter similar to procmail, written in python, using python syntax for mail delivery. .SH OPTIONS .TP .B file1 .B file2 .B ... read mails from file1 file2 ... instead of from stdin (useful for mail postprocessing) .TP .B \-d level, --debuglevel level set debuglevel to level. .TP .B \-t testing mode. No mail delivery will be done. Use in conjuction with .B \-d .TP .B \-c conffile, --config=conffile use file .B conffile as a user configuration file (instead of .B ~/.pycmailrc ) .TP .B \-D define add .B define to the DEFINES list (this can be tested later in the .pycmailrc to modify the behaviour). Can be repeated several times to add more strings. .SH USAGE .B pycmail should be invoked automatically from the .I .forward file when mail arrives. Default debuglevel is 0, which means pycmail tries to deliver mail in almost foolproof way: if there is any error in .I ~/.pycmailrc file, mail will be delivered to the default mailbox (such as .I /var/spool/mail/USERNAME ). If the debuglevel is 1, any error in .I ~/.pycmailrc file will be printed, resulting in bounced mail (but pycmail will still try to deliver the mail to default mailbox). Higher debuglevels will print more information about the actual mail processing. The bigger the debuglevel, the more detailed information is printed. To test your configuration, do something like this: .nf formail -s pycmail -d 2 -t < your_test_mailbox_file .fi .SH "SEE ALSO" .BR procmail "(1), .BR lockfile "(1), .BR formail "(1) .SH AUTHOR Radovan Garab\('ik pycmail-0.1.4/pycmailrc0000644000175000017500000000123410515166367016114 0ustar garabikgarabik00000000000000############################# # this file belongs to /etc # ############################# # default mailbox mail is going to defaultbox = "/var/spool/mail/"+USERNAME # default destination of mail - do not change unless you have a reason # (such as using maildir instead of mailbox as defaul) default = MailBox(defaultbox) # how much bytes after headers to read bodysize = 1000 # size of chunks used to write message to the mail folder. # better make it multiplication of filesystem block size for best performance bufsize = 4096 # location of sendmail program sendmailbin = "/usr/sbin/sendmail" # location of lockfile program lockfilebin = "/usr/bin/lockfile" pycmail-0.1.4/debian/0000755000175000017500000000000011020271020015377 5ustar garabikgarabik00000000000000pycmail-0.1.4/debian/dirs0000644000175000017500000000001411020270132016261 0ustar garabikgarabik00000000000000usr/bin etc pycmail-0.1.4/debian/docs0000644000175000017500000000006510515166372016300 0ustar garabikgarabik00000000000000README README.nopython README.python INSTALL CREDITS pycmail-0.1.4/debian/control0000644000175000017500000000057211020270401017010 0ustar garabikgarabik00000000000000Source: pycmail Section: mail Priority: optional Maintainer: Radovan Garabík Build-Depends: debhelper (>= 4) Standards-Version: 3.7.3 Package: pycmail Architecture: all Depends: python Recommends: procmail Description: mail sorter written in Python mail sorter similar to procmail, written in Python, using Python syntax for mail delivery pycmail-0.1.4/debian/rules0000755000175000017500000000262311020270250016466 0ustar garabikgarabik00000000000000#!/usr/bin/make -f # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 build: build-stamp build-stamp: dh_testdir # Add here commands to compile the package. #$(MAKE) touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp # Add here commands to clean up after the build process. #-$(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs # Add here commands to install the package into debian/tmp. #$(MAKE) install DESTDIR=`pwd`/debian/pycmail cp pycmail `pwd`/debian/pycmail/usr/bin/ cp pycmailrc `pwd`/debian/pycmail/etc/ # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install # dh_testversion dh_testdir dh_testroot # dh_installdebconf dh_installdocs dh_installexamples dh_installmenu # dh_installemacsen # dh_installpam # dh_installinit dh_installcron dh_installman pycmail.1 dh_installinfo # dh_undocumented dh_installchangelogs dh_link dh_strip dh_compress dh_fixperms # You may want to make some executables suid here. # dh_makeshlibs dh_installdeb # dh_perl dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install pycmail-0.1.4/debian/changelog0000644000175000017500000000661311020267513017273 0ustar garabikgarabik00000000000000pycmail (0.1.4) unstable; urgency=low * use class exception instead of a string one (thans to G. Milde) -- Radovan Garabík Sat, 31 May 2008 17:25:28 +0200 pycmail (0.1.3) unstable; urgency=low * added -D option, to use arbitrary defines (thanks to William McVey) * some minor tweaks * fix manpage (closes: #269881) * rename example so that they do not start with a dot (closes: #385689), thanks to Andrei Popescu * fix long overdue conflicting method and attribute name (thanks to David Handy) -- Radovan Garabík Tue, 17 Oct 2006 16:56:21 +0200 pycmail (0.1.2) unstable; urgency=low * fixed bug when regular expressions ignored case sensitivity settings (closes: #157051) -- Radovan Garabik Tue, 22 Oct 2002 19:57:46 +0200 pycmail (0.1.1) unstable; urgency=low * debian package dependencies modified for new python versioning scheme -- Radovan Garabik Sat, 3 Nov 2001 19:59:28 +0100 pycmail (0.1) unstable; urgency=low * introduced timeout for locking mailboxes * use of --config or -c to change default configuration file * can read messages from files, not only from stdin * Reply() and SendMail() can fake Sender: header as well -- Radovan Garabik Sun, 7 May 2000 16:08:04 +0200 pycmail (0.0.9) unstable; urgency=low * fixed bug in calculating length of message -- Radovan Garabik Mon, 10 Apr 2000 16:44:57 +0200 pycmail (0.0.8) unstable; urgency=low * fixed bug when setting a default mailbox from /etc/pycmailrc * separate testing mode (-t) and writing debug messages (-d) -- Radovan Garabik Sun, 2 Apr 2000 21:51:02 +0200 pycmail (0.0.7) unstable; urgency=low * updated examples to reflect changes in new versions * little bug fixed: test for an already existing file when delivering to maildir -- Radovan Garabik Tue, 14 Mar 2000 22:41:44 +0100 pycmail (0.0.6) unstable; urgency=low * support for test runs * protect against errors in user recipes * added Bounce() function -- Radovan Garabik Thu, 9 Mar 2000 11:44:18 +0100 pycmail (0.0.5) unstable; urgency=low * important bug fix: files were not closed properly * deafult searching is now case insensitive for substrings, case sensitive for regular expressions * added SetDefault function -- Radovan Garabik Tue, 7 Mar 2000 10:55:09 +0100 pycmail (0.0.4) unstable; urgency=low * added Reply, SendMail functions * more consistent capitalization in variables and functions -- Radovan Garabik Fri, 3 Mar 2000 14:23:55 +0100 pycmail (0.0.3) unstable; urgency=low * fixed bug in handling regular expressions -- Radovan Garabik Thu, 2 Mar 2000 17:21:18 +0100 pycmail (0.0.2) unstable; urgency=low * Set, Append functions added * changes regarding reading the messgae -- Radovan Garabik Wed, 1 Mar 2000 15:55:19 +0100 pycmail (0.0.1) unstable; urgency=low * Initial Release. -- Radovan Garabik Tue, 29 Feb 2000 12:48:21 +0100 Local variables: mode: debian-changelog End: pycmail-0.1.4/debian/compat0000644000175000017500000000000211020270372016606 0ustar garabikgarabik000000000000004 pycmail-0.1.4/debian/README.Debian0000644000175000017500000000031010515171533017452 0ustar garabikgarabik00000000000000pycmail for Debian ---------------------- Packaged as native package. To use pycmail, put this into your ~/.forward file: |/usr/bin/pycmail -- Radovan Garabík pycmail-0.1.4/debian/copyright0000644000175000017500000000034410515166372017360 0ustar garabikgarabik00000000000000This program is under any license you desire, as long as it does not demand any responsibility or commitments from me, and recognizes me as the author. Radovan Garabik pycmail-0.1.4/debian/examples0000644000175000017500000000022510515172635017163 0ustar garabikgarabik00000000000000examples/example.pycmailrc examples/pycmailrc examples/example.pycmailrc-simple examples/example.pycmailrc-zigi examples/example.pycmailrc-duplicate pycmail-0.1.4/README.nopython0000644000175000017500000001341410515166372016742 0ustar garabikgarabik00000000000000This README is for you if you do not speak python. If you do, look at README.python This text is very brief and not yet finished. The best bet would be to look at examples/ and start modifying those examples. Read something about python syntax. Good URL is http://www.idi.ntnu.no/~mlh/python/instant.html The best way to set up a .pycmailrc file is to use following conditions: if condition: Set(mail_destination(parameter)) Stop() if condition2: Set(mail_destination2(parameter)) Stop() .... Set sets the mail destination. You can use Append instead Set, in this case the destination is appended to already existing one. notice the identation - it is important, and it separates body of the if condition (so there are no curly brackets or anything else). All variables and keywords are case sensitive. The colon : after the condition is compulsory. mail_destination can be one of following: DevNull() no comment Pipe("name") execute program "name" and pass the message (with full headers) to it as stdin. if the program prints anything to stdout, it is taken as error in mail delivery - use Pipe("name >/dev/null 2>&1") if it is undesirable. Forward("name@computer") forward mail to name@computer MailDir("directory") deliver mail to "directory", in maildir format MailBox("mailbox") deliver mail to "mailbox" in BSD mailbox format you can deliver mail to more destinations, separate them with comma , e.g. if InHeader("X-Mailing-list", "debian-devel"): Set( MailBox("/home/username/debian-devel"), Forward("krivanek@dmpc.dbp.fmph.uniba.sk") ) Stop() You can use following conditions: Contains(s, sub): return true if string sub is in string s (using substrings, not regular expressions) Contains(s, sub, case=0) - the same, but case insensitive Contains(s, sub, rex=1) - the same, but using regular expressions example: if Contains(FROM, "spammer@yahoo.com", case=0): Junk() Stop() Junk() is the same as Set(DevNull()) InHeader(hname, sub): return true if sub is in header with name hname InHeader(hname, sub, case=1): case sensitive version example: if InHeader("X-Mailing-list", "debian-devel"): Set(MailBox("/home/username/debian-devel")) Stop() Following strings are defined: FROM - string taken from From: header, with leading and trailing whitespace stripped TO - string taken from TO: header, with leading and trailing whitespace stripped CC - string taken from Cc: header, with leading and trailing whitespace stripped SUBJECT - string taken from Subject: header, with leading and trailing whitespace stripped ADDR_FROM[0] - name taken from From: header ADDR_FROM[1] - mail address taken from From: header e.g. if the header has form From: Guido van Rossum , ADDR_FROM[0] would be "Guido van Rossum" ADDR_FROM[1] would be "guido@python.org" ADDR_TO[0] - name taken from To: header ADDR_TO[1] - mail address taken from To: header ADDR_CC[0] - name taken from Cc: header ADDR_CC[1] - mail address taken from Cc: header BODY - string containing message body (or the beginning of it, if the message is long) msg - string containing the message, including headers (or just the beginning, if the message is too long) Following lists are defined: (never mind if you do not know what a list is :-)) NAMELIST_TO - list of names found in To: header ADDRLIST_TO - list of addresses found in To: header NAMELIST_CC - list of names found in Cc: header ADDRLIST_CC - list of addresses found in Cc: header You can test if name is in the list with following condition: if "bablo@view.net.au" in ADDRLIST_TO: # mailing list - move it to apropriate mailbox Set(MailBox("/home/username/bablo")) Stop() if "Bill Clinton" in NAMELIST_CC: # this mail was sent to me and copy went to # Bill Clinton - it has to be something special :-) Append(MailBox("/home/username/specialbox")) Stop() You can execute external programs either with Pipe destination, or, if you do not want to pass the whole mail message to the program, with os.system. example: if Contains(SUBJECT, "seti") and ADDR_FROM[1] == "root@localhost": os.system("killall setiathome") When the program writes anything to the output, it is taken as an error and mail will bounce. This is an example how to prevent it: if Contains(SUBJECT, "seti") and ADDR_FROM[1] == "root@localhost": os.system("killall setiathome >/dev/null 2>&1") Following functions are defined: SendMail(recipient, From="from@mail.address", sender="sender@mail.address", subject="subject", text="pycmail test mail"): send a mail to recipient, if From is specified, the mail will look like it was sent from From address. (It is not intended for faking mails, but as an effective countermeasure against spam - MTA usually places your true identity in headers anyway). Ok, there are situations where you might want to modify the headers so that you true identity is concealed. In that case, use sender="address" to modify Sender: header. You probably must be in "trusted users" group to be able to do this, it depends on your MTA (this is how it is in exim). Reply(recipient="where@to.reply", From="from@mail.address", sender="who@sent.it", subject="subject", text="pycmail test reply"): reply to the current mail. Unless you specify explicitly recipient, the return address is taken from Reply-To: header, if it does not exist, from From: header. If not specified, subject is "Re: "+ original subject Bounce(text): prints text to stdout, which will cause the MTA to bounce the message Debug(text, level=2): if debuglevel is equal or greater than level, print text. Usually, when receiving mail, this means the mail will bounce with error described by text. exmaple: Debug("I am inside a complicated if-then structure", 2) pycmail-0.1.4/INSTALL0000644000175000017500000000202010515171471015221 0ustar garabikgarabik00000000000000You need working python, reasonably recent version (such as 2.3). If you use ordinary mailboxes (not maildirs), you need lockfile program, which is part of procmail package. lockfile should be installed with appropriate priviledges to be able to lock your mailbox - check out procmail's documentation. Copy pycmail executable somewhere (such as /usr/local/bin) and modify the first line of it to point to your python interpreter. Copy (and modify) pycmailrc to /etc. Pay special attention to location of sendmail and lockfile. Put this into your ~/.forward: |/usr/local/bin/pycmail Some mailers need quotes: "|/usr/local/bin/pycmail" Qmail does this differently (see qmail's documentation). Edit your ~/.pycmailrc file. Look at examples/ directory for some examples. ---------------------------------------------------------------- | Always test your configuration, after you making modifications | | to the configuration files | ---------------------------------------------------------------- pycmail-0.1.4/CHANGES0000777000175000017500000000000010515166414020263 2debian/changelogustar garabikgarabik00000000000000pycmail-0.1.4/examples/0000755000175000017500000000000010515172556016021 5ustar garabikgarabik00000000000000pycmail-0.1.4/examples/pycmailrc0000644000175000017500000000123410515166373017727 0ustar garabikgarabik00000000000000############################# # this file belongs to /etc # ############################# # default mailbox mail is going to defaultbox = "/var/spool/mail/"+USERNAME # default destination of mail - do not change unless you have a reason # (such as using maildir instead of mailbox as defaul) default = MailBox(defaultbox) # how much bytes after headers to read bodysize = 1000 # size of chunks used to write message to the mail folder. # better make it multiplication of filesystem block size for best performance bufsize = 4096 # location of sendmail program sendmailbin = "/usr/sbin/sendmail" # location of lockfile program lockfilebin = "/usr/bin/lockfile" pycmail-0.1.4/examples/example.pycmailrc-simple0000644000175000017500000000145210515166373022652 0ustar garabikgarabik00000000000000# directory where your mailboxes are stored md = USERHOME+"/Mail/" # junk everything from well-known spammer, and everything that has $$$ in Subject: if ADDR_FROM[1] == "spammer@yahoo.com" or Contains(SUBJECT, "$$$"): Junk() Stop() # if body of the mail contains words "business opportunity", send it to # a special mailbox - it is probably spam, but what if it is not - we might # want to look at it later if Contains(BODY, "business opportunity"): Set(MailBox(md+"possible-spam")) Stop() # mail from former girlfriend - forward it to her current boyfriend, # but keep a copy just in case :-) # ADDR_FROM[0] is name, ADDR_FROM[1] e-mail address if ADDR_FROM[0] == "Pamela Anderson": Set( Forward("tommy.lee@hollywood.com"), MailBox(md+"pamela") ) Stop() pycmail-0.1.4/examples/example.pycmailrc0000644000175000017500000000175110515166373021365 0ustar garabikgarabik00000000000000md = USERHOME+"/Mail/" # advanced usage: spawn xtell, and write there the name or address of the sender, # and subject. a=os.popen("xtell "+USERNAME+" >/dev/null 2>&1", "w") a.write("Mail from "+(ADDR_FROM[0] or ADDR_FROM[1])+":"+SUBJECT+"\n") a.close() # well-known spammer if Contains(FROM, "spammer@yahoo.com"): Junk() Stop() # sort mailing list: bablo@view.net.au is the address, it can be either recipient # or in Cc: if "bablo@view.net.au" in ADDRLIST_TO+ADDRLIST_CC: Set(MailBox(md+"bablo")) Stop() # better handled mailing list, it has header X-Mailing-list # we write it to the mailbox and forward to a friend who was too lazy to subscribe by himself # (this is better for the network, too...) if InHeader("X-Mailing-list", "debian-devel"): Set(MailBox(md+"debian-devel"), Forward("krivanek@dmpc.dbp.fmph.uniba.sk")) # this would be equivalent to: #Set(MailBox(md+"debian-devel")) #Append(Forward("krivanek@dmpc.dbp.fmph.uniba.sk")) Stop() pycmail-0.1.4/examples/example.pycmail-popescu0000644000175000017500000000057110515172457022513 0ustar garabikgarabik00000000000000####### # Override the system default set in /etc/pycmailrc default = MailDir("~/Maildir/") # After we specified our default, we need to activate it # documentation is not very clear about this SetDefault() # Sort mailing list to its own maildir using the List-Id: header if InHeader("List-Id", "debian-user"): Set(MailDir("~/Maildir/.Debian-User/")) Stop() ####### pycmail-0.1.4/examples/example.pycmailrc-duplicate0000644000175000017500000000360310515166373023333 0ustar garabikgarabik00000000000000#!/usr/bin/python # simple example to show how to detect duplicate mails # based on contribution by Samuel Krempp import md5, cPickle md = USERHOME+"/Mail/" class MsgCache: def __init__(self, entries = 5): self.data = entries*[0] self.position = 0 self.entries = entries def add(self, value): self.data[self.position] = value self.position = self.position+1 if self.position >= self.entries: self.position = 0 def resize(self, newentries): if newentries == self.entries: return if newentries < self.entries: self.data = self.data[:newentries] # fixme if self.position >= newentries: self.position = newentries-1 else: self.data = self.data + (newentries-self.entries)*[0] self.entries = newentries def Duplicate(hashfile="~/Mail/.msgid_cache", entries=5): "test for duplicate mails" hashfile = os.path.expanduser(hashfile) dotlock(hashfile, retries=6, locktimeout=10) # wait up to 1 minute before forcing lock if os.path.isfile(md+".msgid_cache"): try: fd=open(hashfile, "r") msgcache=cPickle.load(fd) fd.close() except: # something wrong - probably corrupt cache file msgcache=MsgCache() msgcache.resize(newentries=entries) else: msgcache = MsgCache() msg_hash=mailmsg.getheader("Message-ID") or "" msg_hash=msg_hash + FROM + TO msg_hash=md5.new(msg_hash).digest() if msg_hash in msgcache.data: Debug("Duplicate found", 2) Junk() dotunlock(hashfile) return 1 else: msgcache.add(msg_hash) fd=open(hashfile, "w") cPickle.dump(msgcache, fd) fd.close() dotunlock(hashfile) return 0 if Duplicate(): Debug("Duplicate found", 2) Junk() pycmail-0.1.4/examples/example.pycmailrc-zigi0000644000175000017500000000702310515166373022323 0ustar garabikgarabik00000000000000md = USERHOME+"/Mail/" def tell(): "announce just arrived mail if I am logged in" # of course, xtell is installed on the computer a=os.popen("xtell "+USERNAME+" >/dev/null 2>&1", "w") a.write("Mail from %s\n" % (ADDR_FROM[0] or ADDR_FROM[1])) a.write("Subject: %s\n" % SUBJECT) a.close() if "bablo@view.net.au" in ADDRLIST_TO+ADDRLIST_CC: Set(MailBox(md+"bablo")) Stop() if InHeader("X-Mailing-list", "debian"): listaddr = mailmsg.getaddr("X-Mailing-list")[1] sublist = string.split(listaddr, '@')[0] Set( Forward("krivanek@dmpc.dbp.fmph.uniba.sk"), MailBox(md+sublist) ) Stop() if InHeader("X-list", "freeciv-dev"): Set(MailBox(md+"freeciv-dev")) Stop() if InHeader("X-list", "freeciv"): Set(MailBox(md+"freeciv")) Stop() if InHeader("Sender", "owner-atlas"): Set(MailBox(md+"atlas")) Stop() if Contains(FROM, "freshmeat daemon"): Append(Forward("andel@dmpc.dbp.fmph.uniba.sk")) Stop() if ADDR_FROM[1] == "kjf@center.fmph.uniba.sk": Set(MailBox(md+"kjf")) tell() Stop() # simple spam test if not ( Contains(SUBJECT, "this is not a spam") or Contains(SUBJECT, "toto nie je spam") ): spamvalue = 0 if not (Contains(TO, USERNAME) or Contains(CC, USERNAME)): spamvalue = spamvalue+10 Debug("spamfilter: not in To: or Cc:", 4) if mailmsg.getheader('To') == None: spamvalue = spamvalue+5 Debug("spamfilter: empty To:", 4) for spamword in ["FREE", "BUSINESS", "WIN", "RICH", "MONEY", "EARN"]: s = len(re.findall("(?i)\\b"+spamword+"\\b", BODY)) spamvalue = spamvalue+3*s Debug("spamfilter: spamword %s, %i" % (spamword, s), 4) s = len(re.findall("\\b"+spamword+"\\b", BODY)) # 6 additional points if the word is in CAPITAL LETTERS spamvalue = spamvalue+6*s Debug("spamfilter: SPAMWORD %s, %i" % (spamword, s), 4) if Contains(BODY, "1-800"): spamvalue = spamvalue+10 Debug("spamfilter: 1-800 in body", 4) if Contains(SUBJECT, "\\bADV?:", rex=1): spamvalue = spamvalue+15 Debug("spamfilter: ADV in Subject:", 4) Debug("got spamvalue "+`spamvalue`, 2) if spamvalue >= 15: # note that we fake our username, so that spammer will # not get confirmation that this address is really valid Reply(From=USERNAME+".nospam", subject="Possible spam (Re: %s)" % SUBJECT, text= """ Hi I am sorry, but you have just hit my anti-spam filter. Your mail was classified as spam, with spam value %i, while 14 is a limit for non-spam mails. This means your mail was moved to a very infrequently read mailbox. If it is really was no spam, send me a mail again (do not reply to this mail, use the original address), and place words "this is not a spam" somewhere in the Subject: line. Thank you and sorry for the inconvenience. Je mi luto, ale vas mail bol zachyteny mojim anti-spamovym filtrom. Spamovitost vasho mailu bola ohodnotena hodnotou %i, zatial co horna hranica pre legitimne maily je 14. Znamena to ze vas mail bol bez precitania presunuty do velmi zriedkavo citaneho mailboxu. Ak to naozaj nebol spam, poslite mi mail opat (neodpovedajte na tento mail, pouzite originalnu adresu), a umiestnite slova "toto nie je spam" niekde do Subjectu. Dakujem a prepacte pripadne tazkosti ktore vam moj filter sposobil. ZiGi """ % (spamvalue, spamvalue) ) Set(MailBox(md+"spam")) Stop() tell() pycmail-0.1.4/COPYING0000644000175000017500000000034511020267563015233 0ustar garabikgarabik00000000000000This program is under any license you desire, as long as it does not demand any responsibility or commitments from me, and recognizes me as the author. Radovan Garabík pycmail-0.1.4/CREDITS0000644000175000017500000000023011020267630015204 0ustar garabikgarabik00000000000000So far, these people had useful suggestions and contributions to pycmail: Samuel Krempp Phil Hunt Duncan Bellamy Gregor Hoffleit William McVey G. Milde