TLpost.py


This is the current under devlopment version of TLpost.py- this is a ssi include of the actual almost working version in our server NOW.



#!/usr/local/bin/python
#The above line MUST point at the python interpeter!
#
# Tinylist V:1.7.0 Mail List Manager suite, Pitchtime module.
# Officially observed to take up diskspace on 22 dec 2001.
# fined tuned for the remainder of december,
# and released to the public 1 january 2002.
# THIS EDITION IS COPYRIGHT 2003 by Kirk D Bailey,
# and is made available under the terms of the GNU GPL.
#10
# This module only handles receiving and sending out postings to a list.
# management of membership is handled by the favored command module
# 
# Being modular makes for MORE CHOICES AVAILABLE TO YOU!
################################################################################
#           Python can be studied and aquired at http://www.python.org/ !!!
#########1#########2#########3#########4#########5#########6#########7#########8
#
#
#20 ADMIN AND LEGAL STUFF:
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#30
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# You should have received a copy of the GNU General Public License           
# along with this program; if not, write to:
#
#    Free Software Foundation, Inc.
#    59 Temple Place - Suite 330
#    Boston, MA  02111-1307 USA.
#40
# and request one; be cool and include a business sized SASE
# (SASE=Self Addressed Stamped Envlope).
#
# Also, the GNU GPL may be viewed online at http://www.gnu.org/licenses/gpl.html
#
###############################################################################
#
# "The tyrant's foe, the people's friend, a free press." -Dr Benjamin Franklin.
#
#50##############################################################################
#
# DEVLOPERS:
# Kirk Bailey  (daddy)
# Sean 'Shaleh' Perry  (godfather)
# Danny Yoo   (mentor)
#
###############################################################################
#
# INSTALLATION NOTES:
#60 BE CERTAIN to specify CORRECT path infomation or this script WILL NOT RUN!
# note also the VERY FIRST LINE; this tells the script where to find
# Python! This MUST be correct! At the command line prompt, ask your server
# "whereis python"
# and it will tell you. Copy that into the first line IMMEDIATELY after '#!'.
# (You DO have python installed, RIGHT? This will NOT work without it!)
#
# EXCEPT for that first line, you may delete all lines which are entirely
# a comment. SAVE a copy with all the babble for later reference.
# Lines which are partly comment and partly code musr be edited CAREFULLY.
#70
###############################################################################
#
# OWNERSHIP AND PERMISSION ISSUES
# make sure this script runs as a TRUSTED USER-
# and NOT as root!!! You set that up in the Sendmail
# Config file (sendmail.cf).
# Make sure that a NON-priviliged user OWNS
# this script, and that it runs as that identity!
# Generally, this is accomplished by making sure it is owned by that user.
#80
###############################################################################
#
# SPAM
# Spam SUCKS. It also ROBS everyone it touches, each system it passes through.
# Fight spam. DO NOT host open lists all the world may post to.
# TinyList CANNOT do so as written, PLEASE do not defeat this.
#
# Monitor your lists; EJECT members who spam. SUE spammers if you can.
# 
#90 Inconvience spammers by limiting recipients per envlope to around 10
# recipients per envlope in the sendmail.cf file. This will NOT inconvience
# TinyList- each recipient's email get's it's own envlope!!!
#
###############################################################################
#
# TRIBAL CULTURE NOTES
# The TinyList community hangs out at www.tinylist.org
# and is a pretty informal and friendly bunch. Come visit!
# 
#100
# We have lists there, which only seems right somehow.
# they are:
#
#      tinylist-users - discussion for all aspects of using
#                       tinylist; frequently quite technical.
#                       ask install and problem questions here.
#				Newbies welcomed gladly.
#
# tinylist-devlopers -	devlopers use this to suport the
#110				devlopment of new versions, features, 
#				and bugchasing. *ROUTINELY* -VERY-
#				technical. NOI reccomended to newbies.
#
#
# Fnord.
###############################################################################
# we now import some functions from assorted libraries.
import sys, re, string, rfc822, smtplib, os.path, random
# 
#120			     CONFIGURATION SECTION
#                            #####################
#
# NOTE that this script is SUPPOSED to be installed in the web cgi-bin!
# and the lists dir is immediately under this dir!
#
# ok, where am I? I just woke up!
fullpathtoscript = os.path.split(os.path.abspath(sys.argv[0]))
#print 'fullpathtoscript=',fullpathtoscript
# ok, now my config file is supposed to be RIGHT HERE with me!
#130 So let's read the thing!
f1=open(fullpathtoscript[0]+"/tinylist.cf",'r')
# Tell me little file, who am I?
localhost=string.strip(f1.readline())
f1.close()
# knowing where I am, I know that my lists are ONE FLOOR DOWN!
path=fullpathtoscript[0]				#
pathtolists=path+'/lists/'
#print 'Path=',path
# ALL TinyList scripts MUST live in the web cgi-bin, and
#140 ALL global and list files are directly off the web cgi-bin dir in '/lists'.
# that dir should be owned by the same owner and group as this script, and
# should be chmod 766. DIR 'list' must be 766, with all Scripts 755.
#
#
# This ends the configuration portion. Hereafter only hackers need venture.
###############################################################################
#
#
#
#150
#
#
#
#
#
#
#
#
#
#160
#
#
#
#
#
#
#
#
#
#170
#
#
#
#
#
#
#
#
#
#180 WHAT?!? Still reading? ;-)
#
#
# BEGIN PROCESSING PORTION
#######################################################
# Read command line arguement to learn the list name
listname = sys.argv[1]			# command line arguement 1 is listname
version="1.7.0"
#
# we append \r\n a lot, so we create a variable to contain that.
CRLF="\r\n" #190
#
# Using rfc822.py, digest the message and make parts available to the program.
Message = rfc822.Message(sys.stdin)
#
#
# Extract the FROM data and copy it to a second storage variable to use later.
RawSender = string.strip(Message['From'])
#
#
#200 preserve RawSender for later use,
# but process sender for the pure
# email address for membership testing.
#
# extract the pure address:
match = re.search(r'([^ \"\'<]+@[^ \"\'>]+)', RawSender )
if match != None:
	sender = match.group(1)
else:
	print "ERROR: invalid FROM field. ABORTING PROCESS."
	sys.exit('Invalid FROM in TLpost.py')	#210
#
def gangstrip(thing):                   # ok, everybody STRIP!
        index=0                         # This strips out whitespace chars
        while index < len(thing):       # define exit
                thing[index]=string.strip(thing[index])
                index=index+1           # increase the counter
#
if os.path.exists(pathtolists+listname+'.nongrata'):	# if there is a banned file
	f1=open(pathtolists + listname + '.nongrata')	# open it,
	badboys = f1.readlines()				#220 read it in,
	f1.close()						# close the file,
	gangstrip(badboys)					# clean up the contents
	if sender in badboys:					# and if the sender is in there,
		sys.exit()						# just quit, do not process
#									# the message.

#
# That means assign the value of the pure email address from the From: 
# field to sender, IF THERE IS A VALUE,
#230 and EXIT if there is NOT a value- no anynomous postings!
#
# Let's extract the subject for later use!
try:
	subject= string.strip(Message['Subject'])
except KeyError:
	subject="(none provided by sender!- Tiny)"
#
#
# print "Subject=",subject
#240 The information is in the dictionary 'Message', each item keyed with the
# header's name. The BODY of that dictionary is in the string 'msg'. So are
# any attachments.
#
# and define fileoneline, which reads a file, selects one line at random,
# and returns that as it's value.
def fileoneline(filename):		# this returns one line from a file.
	f1=open(filename,'r')			#open it,
	db=f1.readlines()				# read it all in
	f1.close()					# close it,
	return random.choice(db)			#250 and spew back one of n items.
#
# ok, let's read that membership file. it is named (listname)
# with no name extension.
f1 = open(path + "/lists/" + listname, 'r')						
members = f1.readlines()		# read all of it in.
f1.close()				# and close the file
#
#
# clean it up;
gangstrip(members)			# 260 strip all leading and trailing whitespace
					# from each element in the list 'members'.
#
# Ok, now we can start some real data processing!
msg=""	 				# initiate a blank STRING for the message.
#
# then we look to see if that FROM exists in the membership.
#
if sender in members:	#
	# print "Sender is a member of this list!" # testing code commented out
	#270 at least we know they are a member if they come here.
	# then we see if there is a post restriction file,
	if os.path.exists(path + "/lists/" + listname + ".ok2post"):
		#And if there is, we read it and see if the poster is in it!
		f1=open(path + "/lists/" + listname + ".ok2post",'r')
		postok = f1.readlines()
		gangstrip(postok)
		if sender not in postok:
			sys.exit(0)	#
	#
	#280
	# if none, or sender is present in it, continue...
	#
	# we will use from_addr in any outgoing email, so define it as RawSender.
	from_addr = RawSender
	msg = "From: " + RawSender + CRLF	# this places a from IN the letter.
	#
        # 
        # Use OPTIONAL reply-to field?
        if os.path.exists(path + "/lists/" + listname + ".replyto"):	# if exists, 
                f1=open(path + "/lists/" + listname + ".replyto",'r')		#290 open it
                xx = string.strip(f1.readline()) + CRLF				# read it,
                f1.close()									# close it,
                print 'reply-To: ',xx							# add the header,
                msg = msg + "Reply-To: "+ xx						# and the contents as the value.
	#
	msg = msg + "To: " + listname + "@" + localhost + CRLF
	#
        # if "RE:" is in the subject, do not append more subject!
        if not re.search("^\s*[Rr][Ee]:?\s+", subject):	# Deep magic here
                # prepend "[listname] " to subject line.	#300
                msg = msg + 'Subject: [' + listname + '] ' + subject + CRLF
        else:   #if there is a RE: in the subject, do not add to it, just retain it.
                msg= msg + 'Subject: ' + subject + CRLF
	# Break email loops with other list servers from forming. detected 9 lines down.
	msg = msg + "X-MLM:Tinylist V:"+ version + CRLF			   # Shameless self promotion
	msg = msg + "X-Loop: " + listname + "@" + localhost + CRLF	   # breaks mail loops
	msg = msg + "List-ID: <'" + listname + "@" + localhost + "'>" + CRLF # aids filtering
	msg = msg + "X-website: Visit tinylist at our website: http://www.tinylist.org/" + CRLF
	msg = msg + "Precedence: Bulk" + CRLF + CRLF
	#310
	# If the incoming message has a X-Loop header, QUIT!
	# this breaks Mailing List Loops- usually. Some servers do not detect, alas...
	# REGRETFULLY, some MLM's do not append this- or detect it.
	if Message.has_key('X-Loop'):
		 sys.exit(0)
	#
	# Likewise, some MLM's use Bulk precedence to break loops,
	# so detecth this also.
	#
	if Message.has_key('Precedence'):				#320
		precedence=string.strip(Message['Precedence'])		#
		print 'Precedence=' + precedence			# debug code
		if precedence == 'Bulk':				# this also helps break loops.
			sys.exit(0)					# do not service bulk mail input.
	#PREFACES
	# now add the preface in front of the body IF there is one!
        if os.path.exists(path + "/lists/global.preface"):
                f1=open(path + "/lists/global.preface")
                preface = string.join(f1.readlines(),"")
                f1.close()                              #330
                msg = msg + preface			#
	if os.path.exists(path + "/lists/" + listname + ".preface"):
                f1=open(path + "/lists/"+listname + ".preface")
                preface = string.join(f1.readlines(),"")
                f1.close()				#
                msg = msg + preface			#
        # HEY, be cool, that "" is needed. Anything in there is stuck in
        # the output after EACH CHAR!!!
	if os.path.exists(path + "/lists/global.randompreface"):
                msg = msg + fileoneline(path + "/lists/global.randompreface") + CRLF #340
	#
	if os.path.exists(path + "/lists/" + listname + ".randompreface"):
	 	msg = msg + fileoneline(path + "/lists/" + listname + ".randompreface") + CRLF
	#
	# Now we will add the body extracted form the incoming message.
	# see http://www.python.org/doc/current/lib/message-objects.html
	# also see rfc822 module for more details.
	# append incoming message body to outgoing message.
	# sets pointer to start of body.
	#350
	msg = msg + string.join( Message.fp.read(),"")	# add the body  of the message in.
	#
	# rewindbody() rewinds to the start of the body.
	# We use it if we are going to read the body a SECOND time!
	# (which in this application we will not, but we left it in here
	# for the enterainment of our gentle reader.)
	# the footer material is appended AFTER the incoming message,
	# so if there is an attachment, all
	# bloody hell will erupt- which is normal for list managers.
	#360
	#
	# FOOTERS
	# ok, here are 4 items that are optional.
	# a per list footer,
	# a per list rotation,
	# a global footer,
	# and a global rotation.
	# these are turned on and off by the presence or absense of the
	# relevant file.
	#370 If you want more than one, duplicate that block, and change the
	# filename reference. Remember, hackers only down here, good luck,
	# yer on yer own, but feel free to send us a shrieking plea for help
	# on the tinylist-users list.
	# Um, that's tinylist-users@tinylist.org, and only members may post.
	# hint- subscribe, at http://www.tinylist.org/cgi-bin/TLwebform2.py?tinylist-users !
	#
	# now, on to the footers!
	# the random global footer is usually used to generate revenues with text ads
	# on the site's list service. This should be up here where it is more
	# likely to be read, as the reader looks for the footer.
	#
        # append ONE line from the GLOBAL ramdom rotation line if there is one.
        # IF it exists, EVERY list you handle will get it.
        if os.path.exists(path + "/lists/global.random"):
                msg = msg + fileoneline(path + "/lists/global.random") #
	#
	# Now append the list's own pesonalized static footer if there is one.
	# (if not, we will create a intelligent footer with pertenint data tossed in!)
	#
	if os.path.exists(path + "/lists/" + listname + ".footer"):	# if there is a custom footer;
		f1=open(path + "/lists/" + listname + ".footer")	# open it,
		ftr = string.join(f1.readlines(),"")				#380 read it in,
		f1.close()								# close the file,
		msg = msg + ftr + CRLF							# and add the custom footer
	else:										# otherwise, GENERATE ONE!
		if os.path.exists(path+'/lists/'+listname+'.ok2post'):
			msg=msg+"""
------------------------------------------------------------------
 Posting to this list is restricted; regular members may not post.

"""
		else:
			if os.path.exists(path+"/lists/"+listname+".replyto"):
				msg=msg+"""
------------------------------------------------------------------
This is the '"""+listname+"""' list. Click [REPLY] to reply to the list.

"""
			else:
				msg = msg +"""
------------------------------------------------------------------
 This is the '""" + listname + """' list. mailto:"""+ listname + "@" + localhost + """
 Click [REPLY] to send to poster, [REPLYALL] to send to list.

"""
	# 
	# note this is a default per (listname).footer if you do not create one.
	# to override it, just create an empty (listname).footer file!
 	#
	#
	# append one line from the list's random rotation file if there is one.
	# THERE IS NO DEFAULT PER LIST RANDOM FOOTER!
	#
	if os.path.exists(path + "/lists/" + listname + ".random"):	#400
		msg = msg + fileoneline(path + "/lists/" + listname + ".random") + CRLF
	#
	#
	# append the service's GLOBAL footer if there is one. EVERY list will
	# get this same footer if it exists. THERE IS NO DEFAULT GLOBAL FOOTER.
	if os.path.exists(path + "/lists/global.footer"): #if there is a global footer,
		f1=open(path + "/lists/global.footer")		# open it,
		ftr = string.join(f1.readlines(),"")		# read it in,
		f1.close()					# close the file,
		msg=msg + ftr + CRLF				#410 and append it to the message.
	# Now apply the global management page link soon to be demanded by law
	#
	msg=msg+"""
 Manage your membership to this list at
     http://www."""+localhost+"/cgi-bin/TLwebform2.py?"+listname+"""

 Powered by TinyList """ + version + """! http://www.tinylist.org/

"""
	# ###############END OF FOOTERS#################
	# other additions are certainly possible.
	# includes of other files are simple to do.
	# a DYNAMIC content markup language along the lines of ssi in html
	# is an evil concept festering in the back of kirks's mind, but not
	# in this release, or anytime soon, alas.
	# this ends processing for an acceptable posting.
	# Next block handles process for
	# REJECTED postings.
	#begin processing for a non member posting.
	
else:	# BUT IF THEY ARE NOT A MEMBER...
	# print "Not a member!"
	members = [RawSender]
	#430 put poster address as the ONLY recipient in list!
	msg = ""			# and clear the mesage.
	from_addr = "tinylist@" + localhost
	msg = msg + "From: " + from_addr + CRLF
	msg = msg + "Subject: Unauthorized posting to list: " + listname + CRLF
	msg = msg + "Reply-to: postmaster@" + localhost + CRLF
	msg = msg + "To: " + RawSender + CRLF
	msg = msg + "X-Loop: postmaster@" + localhost + CRLF
	msg = msg + "Precedence: Bulk" + CRLF			#430
	msg = msg + CRLF + """To whom it may concern;
Sorry, but as you are not a current member of """ + listname + """, 
you may not post to it. Your recent posting has been rejected and
destroyed. Sorry if this causes any inconvience.

Feel free to contact the postmaster if there is any question.
Any reply to this letter should go directly to the postmaster.

Goodbye.
	-Tiny the list robot.

"""	#450
#

# End of alternate process. ABOVE IS 1 BLANK LINE, DO NOT DELETE IT!
# ok, if they are not a member, THEY GET 
# THE REPLY SHOWN ABOVE MAILED TO THEM! 
# (Maybe we could read a stock answer from a file? next version...)
#
# The message is ready. Let us store it in the archive.
#
if os.path.exists(path + "/lists/" + listname + ".archive"):	#460 if the archive file exists,
	f1=open(path + "/lists/" + listname + ".archive",'a')		# open the file for appending
	f1.write(msg)							# write the message to it,
	f1.write("--next message\r\n") 					# and a seperator,
	f1.close()							# then close the file.
#
# now we send whatever message is to go out to 
# whatever is in the recipient list.
#
server = smtplib.SMTP(localhost)
#470
for to_addr in members :		# for each address in 'listnamemembers',
	# print "to_addr: ", to_addr (testing code, commented out, these 3 lines)
	# print "from_addr: ", from_addr
	# print "msg=" + CRLF, msg
	try:
		print 'Serving email to:'+to_addr		#
		server.sendmail(from_addr, to_addr, msg)	# send envlope and msg!
	except Exception, e:					#
		print "POSSIBLE Bad address='"+to_addr+"' ?" 	#
		print str(e)					#480
		from_addr2="From: TLpost.py@"+localhost
		to_addr2='postmaster@'+localhost
		msg2="To: "+ to_addr2 + CRLF
		msg2=msg2 + 'Subject: Possible error while serving:'+listname+CRLF+CRLF
		msg2 = msg2 + """Dear Postmaster;
Just now I had some trouble servicing traffic on the
list '"""+listname+""" with the address '""" + to_addr + """'
as the current guest of honor.

Also, there was an error message returned, and it was

---------------------------------------------------------
""" + str(e) + """
---------------------------------------------------------

which I have to ask you to look at and interpet.

Respectfully, your obedient little friend,
					    -Tiny.

"""
		server.sendmail(from_addr2, to_addr2, msg2)

# don't delete the above line!
server.quit()				# then close the connection.
#
# fnord. Remember, the TAO is your everyday mind.
#
# Peace.
#510




ODD#kdb/TLpost.shtml