pycmail/0000755000000000000000000000000013001161737007401 5ustar pycmail/README0000644000000000000000000001115113001161642010253 0ustar Written by Radovan Garabík For new versions, look at http://kassiopeia.juls.savba.sk/~garabik/software/pycmail.html Note: this software is old, it originated in the days when python and maildir were a cool novelty, RAM was scarce, things were simpler, python did not have modules dealing with mail and MIME was frowned upon. While the program works quite well, I would implement it very differently nowadays. 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. You can log delivery of messages by specifying logging file by including logfile = open("/path/to/logfile.log","a") at the beginning of your ~/.pycmailrc 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/examples/0000755000000000000000000000000010515172556011227 5ustar pycmail/examples/example.pycmailrc-duplicate0000644000000000000000000000360310515166373016541 0ustar #!/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/examples/pycmailrc0000644000000000000000000000123410515166373013135 0ustar ############################# # 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/examples/example.pycmailrc-simple0000644000000000000000000000145210515166373016060 0ustar # 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/examples/example.pycmailrc-zigi0000644000000000000000000000702310515166373015531 0ustar md = 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/examples/example.pycmail-popescu0000644000000000000000000000057110515172457015721 0ustar ####### # 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/examples/example.pycmailrc0000644000000000000000000000175110515166373014573 0ustar md = 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/pycmail0000711000000000000000000002723011414356662010772 0ustar #! /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, datetime 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="", returnPath=""): self.destinyname = destinyname self.returnPath = returnPath 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): if self.returnPath: self.returnPath = " -f " + self.returnPath Debug("Destiny: %s, ReturnPath: %s" % (self.destinyname, self.returnPath)) cmdLine = sendmailbin+self.returnPath+" -i "+self.destinyname Debug("Command Line: "+cmdLine) self.fd = os.popen(cmdLine, "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 Log("DEBUG:"+text) def Log(text): if logfile: date = datetime.datetime.now().strftime("%Y/%m/%d - %H:%M:%S") logfile.write("%s: %s\n" % (date, 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] #LOGFILENAME = USERHOME + "/.pycmail-log" LOGFILENAME = None # This is to disable logging 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" if LOGFILENAME: logfile = open(LOGFILENAME,"a") else: logfile = None 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() Log("Message from: %s / To: %s / Subject: %s" % (FROM, TO, SUBJECT)) # 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: Log("Destination: %s" % (i.name())) 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) if logfile: Log("--------") logfile.close() pycmail/INSTALL0000644000000000000000000000201713001161624010425 0ustar You 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 privileges 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/COPYING0000644000000000000000000000034511020267563010441 0ustar This 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/pycmailrc0000644000000000000000000000123410515166367011322 0ustar ############################# # 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/CREDITS0000644000000000000000000000023011020267630010412 0ustar So far, these people had useful suggestions and contributions to pycmail: Samuel Krempp Phil Hunt Duncan Bellamy Gregor Hoffleit William McVey G. Mildepycmail/README.python0000644000000000000000000001255313001161713011601 0ustar This 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 matching, 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 Forward("name@computer", "return@path") forward mail to name@computer, call sendmail with -f return@path use to set return path when MTA happens to reset it: Append(Forward("me@addr", mailmsg.getaddr('Reply-To:')[1] or ADDR_FROM[1])) 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/CHANGES0000777000000000000000000000000013001154546013465 2debian/changelogustar pycmail/README.nopython0000644000000000000000000001341513001161666012143 0ustar This 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 indentation - 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. example: Debug("I am inside a complicated if-then structure", 2) pycmail/pycmail.10000644000000000000000000000333110515173175011127 0ustar .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/debian/0000755000000000000000000000000013001161737010623 5ustar pycmail/debian/copyright0000644000000000000000000000044013001156705012553 0ustar This 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 Copyright: Copyright © 2000--2016 Radovan Garabík pycmail/debian/examples0000644000000000000000000000022510515172635012371 0ustar examples/example.pycmailrc examples/pycmailrc examples/example.pycmailrc-simple examples/example.pycmailrc-zigi examples/example.pycmailrc-duplicate pycmail/debian/install0000644000000000000000000000004013001160631012176 0ustar pycmail /usr/bin pycmailrc /etc pycmail/debian/docs0000644000000000000000000000006510515166372011506 0ustar README README.nopython README.python INSTALL CREDITS pycmail/debian/manpages0000644000000000000000000000001313001160767012335 0ustar pycmail.1 pycmail/debian/control0000644000000000000000000000061313001161070012214 0ustar Source: pycmail Section: mail Priority: optional Maintainer: Radovan Garabík Build-Depends: debhelper (>= 9) Standards-Version: 3.9.6 Package: pycmail Architecture: all Depends: ${misc: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/debian/changelog0000644000000000000000000000752513001157775012515 0ustar pycmail (0.1.6) unstable; urgency=high * increase debhelper compat version (closes: #817636) * update URLs and addresses in the documentation -- Radovan Garabík Mon, 17 Oct 2016 16:20:41 +0200 pycmail (0.1.5) unstable; urgency=low * add simple logging * add setting envelope sender address (both thanks to Luar Roji) -- Radovan Garabík Mon, 05 Jul 2010 15:21:23 +0200 pycmail (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/debian/compat0000644000000000000000000000000213001154621012013 0ustar 9 pycmail/debian/dirs0000644000000000000000000000001411020270132011467 0ustar usr/bin etc pycmail/debian/README.Debian0000644000000000000000000000031213001156545012661 0ustar pycmail for Debian ---------------------- Packaged as native package. To use pycmail, put this into your ~/.forward file: |/usr/bin/pycmail -- Radovan Garabík pycmail/debian/rules0000755000000000000000000000003513001143045011671 0ustar #!/usr/bin/make -f %: dh $@