diff options
39 files changed, 3308 insertions, 0 deletions
@@ -0,0 +1,97 @@ +0.91: (Dec 23 1996) + - broke code into mixins for managability + - tag parsing instead of lots of gsubs + - tweaked pipermail (see comments on pipermail header) + - templates are now on a per-list basis as intended. + - request over web that your password be emailed to you. + - option so that web subscriptions require email confirmation. + - wrote a first pass at an admin interface to configurable variables. + - made digests mime-compliant. + - added a FakeFile class that simulates enough of a file object on a + string of text to fool rfc822.Message in non-seek mode. + - changed OutgoingMessage not to require its args in constructor. + - added an admin request DB interface. + - clearly separated the internal name from the real name. + - replaced lots of ugly, redundant code w/ nice code. + (added Get...Email() interfaces, GetScriptURL, etc...) + - Wrote a lot of pretty html formatting functions / classes. +(Dec 27 1997) + - Fleshed out the newlist command a lot. It now mails the new list + admin, and auto-updates the aliases file. + - Made multiple owners acceptable. + - Non-advertised lists, closed lists, max header length, max msg length + - Allowed editing templates from list admin pages. + - You can get to your info page from the web even if the list is closed. + +0.92: (Jan 13-16 1997) + - Added Lock and Unlock methods to list to ensure each operation is atomic + - Added a cmd that rms all files of a mailing list (but not the aliases) + - Fixed subscribing an unknown user@localhost (confirm this) + - Changed the sender to list-admin@... to ensure we avoid mail loops. + - check to make sure there are msgs to archive before calling pipermail. + - started using this w/ real mailing lists. + - Added a cron script that scours the maillog for User/Host unknown errs + - Sort membership lists + - Always display digest_is_default option + - Don't slam the TO list unless you're sending a digest. + - When making digest summaries, if missing sender name, use their email. + - Hacked in some protection against crappy dates in pipermail.py + - Made it so archive/digest volumes can go up monthly for large large lists. + - Number digest messages + - Add headers/footers to each message in digest for braindead mailers + - I removed some forgotten debug statements that caused server errors + when a CGI script sent mail. + - Removed loose_matches flag, since everything used it. + - Fixed a problem in pipermail if there was no From line. + - In upvolume_ scripts, remove INDEX files as we leave a volume. + - Threw a couple of scripts in bin for generating archives from majordomo's + digest-archives. I wouldn't recommend them for the layman, though, they + were meant to do a job quickly, not to be usable. + +0.93: (Jan 18,20 1997) + - When delivering to list, don't call sendmail directly. Write to a file, + and then run the new deliver script, which forks and exits in the parent + immediately to avoid hanging when delivering mail for large lists, so that + large lists don't spend a lot of time locked. + - GetSender() no longer assumes that you don't have an owner-xxx address. + - Fixed unsubscribing via mail. + - Made subscribe via mail generate a password if you don't supply one. + - Added an option to clobber the date in the archives to the date the list + resent the post, so that the archive doesn't get mail from people sending + bad dates clumped up at the beginning or end. + - Added automatic error message processing as an option. Currently logging to /tmp/bounce.log + - Changed archive to take a list as an argument, (the old way was broken) + - Remove (ignore) spaces in email addresses + - Allow user passwords to be case insensitive. + - Removed the cleanup script since it was now redundant. + - Fixed archives if there were no archives. + - Added a Lock() call to Load() and Create(). This fixes the problem of loading then locking. + - Removed all occurances of Lock() except for the ones in maillist since creating a list + now implicitly locks it. + - Quote single periods in message text. + - Made bounce system handle digest users fairly. + +0.94: (Jan 22, 1997) + - Made admin password work ubiquitously in place of a user password. + - Added an interface for getting / setting user options. + - Added user option to disable mime digests (digested people only) + - Added user option to not receive your own posts (nondigested people only) + - Added user option to ack posts + - Added user option to disable list delivery to their box. + - Added web interface to user options + - Config number of sendmail spawns on a per-list basis + - Fixed extra space at beginning of each message in digests... + - Handled comma separated emails in bounce messages... + - Added a FindUser() function to MailList. Used it where appropriate. + - Added mail interface to setting list options. + - Added name links to the templates options page + - Added an option so people can hide their names from the subscription list. + - Added an answer_majordomo_mail script for people switching... + + +0.95: (Jan 25, 1997) + - Fixed a bug in sending out digests added when adding disable mime option. + - Added an option to not notify about bounced posts. + - Added hook for pre-posting filters. These could be used to + auto-strip signatures. I'm using the feature to auto-strip footers + that are auto-generated by mail received from another mailing list. diff --git a/Mailman/Errors.py b/Mailman/Errors.py new file mode 100644 index 000000000..8747b3e60 --- /dev/null +++ b/Mailman/Errors.py @@ -0,0 +1,16 @@ +MMBadEmailError = "MMBadEmailError" +MMMustDigestError = "MMMustDigestError" +MMCantDigestError = "MMCantDigestError" +MMNotAMemberError = "MMNotAMemberError" +MMListNotReady = "MMListNotReady" +MMNoSuchUserError = "MMNoSuchUserError" +MMBadPasswordError = "MMBadPasswordError" +MMNeedApproval = "MMNeedApproval" +MMHostileAddress = "MMHostileAddress" +MMAlreadyAMember = "MMAlreadyAMember" +MMPasswordsMustMatch = "MMPasswordsMustMatch" +MMAlreadyDigested = "MMAlreadyDigested" +MMAlreadyUndigested = "MMAlreadyUndigested" +MMBadRequestId = "MMBadRequestId" +MMWebSubscribeRequiresConfirmation = "MMWebSubscribeRequiresConfirmation" + diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py new file mode 100644 index 000000000..503ad9e15 --- /dev/null +++ b/Mailman/Mailbox.py @@ -0,0 +1,16 @@ +import mailbox + +class Mailbox(mailbox.UnixMailbox): + # msg should be an rfc822 message or a subclass. + def AppendMessage(self, msg): + # seek to the last char of the mailbox + self.fp.seek(1,2) + if self.fp.read(1) <> '\n': + self.fp.write('\n') + self.fp.write(msg.unixfrom) + for line in msg.headers: + self.fp.write(line) + if msg.body[0] <> '\n': + self.fp.write('\n') + self.fp.write(msg.body) + diff --git a/Mailman/Message.py b/Mailman/Message.py new file mode 100644 index 000000000..21246dfb0 --- /dev/null +++ b/Mailman/Message.py @@ -0,0 +1,150 @@ +import sys +import rfc822, string, time + + +# A utility function 2 of these classes use: +def AddBackNewline(str): + return str + '\n' + + +# If we're trying to create a message object from text, we need to pass +# a file object to rfc822.Message to get it to do its magic. Well, +# to avoid writing text out to a file, and having it read back in, +# here we define a class that will fool rfc822 into thinking it's a non-seekable +# message. +# The only method rfc822.Message ever calls on a non-seekable file is +# readline. It doesn't use the optional arg to readline, either. +# In my subclasses, I use the read() method, and might just use readlines() +# someday. +# +# It might be useful to expand this into a full blown fully functional class. + +class FakeFile: + def __init__(self, text): + self.lines = map(AddBackNewline, string.split(text, '\n')) + self.curline = 0 + self.lastline = len(self.lines) - 1 + def readline(self): + if self.curline > self.lastline: + return '' + self.curline = self.curline + 1 + return self.lines[self.curline - 1] + def read(self): + startline = self.curline + self.curline = self.lastline + 1 + return string.join(self.lines[startline:], '') + def readlines(self): + startline = self.curline + self.curline = self.lastline + 1 + return self.lines[startline:] + + +# We know the message is gonna come in on stdin or from text for our purposes. +class IncomingMessage(rfc822.Message): + def __init__(self, text=None): + if not text: + rfc822.Message.__init__(self, sys.stdin, 0) + self.body = self.fp.read() + else: + rfc822.Message.__init__(self, FakeFile(text), 0) + self.body = self.fp.read() + + def GetSender(self): + # Look for a Sender field. + sender = self.getheader('sender') + if sender: + realname, mail_address = self.getaddr('sender') + else: + try: + realname, mail_address = self.getaddr('from') + except: + # The unix from line is all we have left... + if self.unixfrom: + return string.lower(string.split(self.unixfrom)[1]) + + return string.lower(mail_address) + + def GetSenderName(self): + real_name, mail_addr = self.getaddr('from') + if not real_name: + return self.GetSender() + return real_name + + def SetHeader(self, name, value, crush_duplicates=1): + # Well, we crush dups in the dict no matter what... + name = "%s%s" % (name[0], name[1:]) + self.dict[string.lower(name)] = value + if value[-1] <> '\n': + value = value + '\n' + + if not crush_duplicates: + self.headers.append('%s: %s' % (name, value)) + return + for i in range(len(self.headers)): + if (string.lower(self.headers[i][:len(name)+1]) == + string.lower(name) + ':'): + self.headers[i] = '%s: %s' % (name, value) + +# This is a simplistic class. It could do multi-line headers etc... +# But it doesn't because I don't need that for this app. +class OutgoingMessage: + def __init__(self, headers=None, body='', sender=None): + self.cached_headers = {} + if headers: + self.SetHeaders(headers) + else: + self.headers = [] + self.body = body + self.sender = sender + + def SetHeaders(self, headers): + self.headers = map(AddBackNewline, string.split(headers, '\n')) + + def CacheHeaders(header, s=self): + i = string.find(header, ':') + s.cached_headers[string.lower(string.strip(header[:i]))] = \ + header[i+2:] + map(CacheHeaders, self.headers) + + def SetHeader(self, header, value, crush_duplicates=1): + if value[-1] <> '\n': + value = value + '\n' + if crush_duplicates: + # Run through the list and make sure the header isn't already there. + remove_these = [] + for item in self.headers: + f = string.find(item, ':') + if string.lower(item[:f]) == string.lower(header): + remove_these.append(item) + for item in remove_these: + self.headers.remove(item) + del remove_these + self.headers.append('%s%s: %s' % (string.upper(header[0]), + string.lower(header[1:]), + value)) + self.cached_headers[string.lower(header)] = value + + def SetBody(self, body): + self.body = body + + def AppendToBody(self, text): + self.body = self.body + text + + def SetSender(self, sender, set_from=1): + self.sender = sender + if not self.getheader('from') and set_from: + self.SetHeader('from', sender) + + def SetDate(self, date=time.ctime(time.time())): + self.SetHeader('date', date) + + def GetSender(self): + return self.sender + +# Lower case the name to give it the same UI as IncomingMessage +# inherits from rfc822 + def getheader(self, str): + str = string.lower(str) + if not self.cached_headers.has_key(str): + return None + return self.cached_headers[str] diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py new file mode 100644 index 000000000..83e27d009 --- /dev/null +++ b/Mailman/SecurityManager.py @@ -0,0 +1,59 @@ +import crypt, types, string, os +import mm_err, mm_utils, mm_cfg + +class SecurityManager: + def SetSiteAdminPassword(self, pw): + old = os.umask(0700) + f = open(os.path.join(mm_cfg.MAILMAN_DIR, "adm.pw"), "w+") + f.write(crypt.crypt(pw, mm_utils.GetRandomSeed())) + f.close() + os.umask(old) + + def CheckSiteAdminPassword(self, str): + try: + f = open(os.path.join(mm_cfg.MAILMAN_DIR, "adm.pw"), "r+") + pw = f.read() + f.close() + return crypt.crypt(str, pw) == pw + # There probably is no site admin password if there was an exception + except: + return 0 + + def InitVars(self, crypted_password): + # Configurable, however, we don't pass this back in GetConfigInfo + # because it's a special case as it requires confirmation to change. + self.password = crypted_password + + # Non configurable + self.passwords = {} + + def ValidAdminPassword(self, pw): + if self.CheckSiteAdminPassword(pw): + return 1 + return ((type(pw) == types.StringType) and + (crypt.crypt(pw, self.password) == self.password)) + + def ConfirmAdminPassword(self, pw): + if(not self.ValidAdminPassword(pw)): + raise mm_err.MMBadPasswordError + return 1 + + def ConfirmUserPassword(self, user, pw): + if self.ValidAdminPassword(pw): + return 1 + if not user in self.members and not user in self.digest_members: + user = self.FindUser(user) + if string.lower(pw) <> string.lower(self.passwords[user]): + raise mm_err.MMBadPasswordError + return 1 + + def ChangeUserPassword(self, user, newpw, confirm): + self.IsListInitialized() + addr = self.FindUser(user) + if not addr: + raise mm_err.MMNotAMemberError + if newpw <> confirm: + raise mm_err.MMPasswordsMustMatch + self.passwords[addr] = newpw + self.Save() + diff --git a/Mailman/aliases.py b/Mailman/aliases.py new file mode 100644 index 000000000..8764912a8 --- /dev/null +++ b/Mailman/aliases.py @@ -0,0 +1,41 @@ +# This is mailman's interface to the alias database. + +# TODO: + +# Write a wrapper program w/ root uid that allows the mailman user +# only to update the alias database. + +import string +_file = open('/etc/aliases', 'r') +_lines = _file.readlines() +aliases = {} +_cur_line = None + +def _AddAlias(line): + line = string.strip(line) + if not line: + return + colon_index = string.find(line, ":") + if colon_index < 1: + raise "SyntaxError", "Malformed /etc/aliases file" + alias = string.lower(string.strip(line[:colon_index])) + rest = string.split(line[colon_index+1:], ",") + rest = map(string.strip, rest) + aliases[alias] = rest + +for _line in _lines: + if _line[0] == '#': + continue + if _line[0] == ' ' or _line[0] == '\t': + _cur_line = _cur_line + _line + continue + if _cur_line: + _AddAlias(_curline) + _cur_line = _line + +def GetAlias(str): + str = string.lower(str) + if not aliases.has_key(str): + raise KeyError, "No such alias" + return aliases[str] + diff --git a/Mailman/pipermail.py b/Mailman/pipermail.py new file mode 100644 index 000000000..ab92a7e29 --- /dev/null +++ b/Mailman/pipermail.py @@ -0,0 +1,552 @@ +#!/usr/local/bin/python +# Hey Emacs, this is -*-Python-*- code! +# +# Pipermail 0.0.2-mm +# +# Some minor mods have been made for use with the Mailman mailing list manager. +# All changes will have JV by them. +# +# (C) Copyright 1996, A.M. Kuchling (amk@magnet.com) +# Home page at http://amarok.magnet.com/python/pipermail.html +# +# HTML code for frames courtesy of Scott Hassan (hassan@cs.stanford.edu) +# +# TODO: +# * Prev. article, next. article pointers in each article +# * I suspect there may be problems with rfc822.py's getdate() method; +# take a look at the threads "Greenaway and the net (fwd)" or +# "Pillow Book pictures". To be looked into... +# * Anything else Hypermail can do that we can't? +# * General code cleanups +# * Profiling & optimization +# * Should there be an option to enable/disable frames? +# * Like any truly useful program, Pipermail should have an ILU interface. +# * There's now an option to keep from preserving line breaks, +# so paragraphs in messages would be reflowed by the browser. +# Unfortunately, this mangles .sigs horribly, and pipermail doesn't yet +# put in paragraph breaks. Putting in the breaks will only require a +# half hour or so; I have no clue as to how to preserve .sigs. +# * Outside URLs shouldn't appear in the display frame. How to fix? +# + +VERSION = "0.0.2.mm" + +import posixpath, time, os, string, sys, rfc822 + +# JV -- to get HOME_PAGE +import mm_cfg + +class ListDict: + def __init__(self): self.dict={} + def keys(self): return self.dict.keys() + def __setitem__(self, key, value): + "Add the value to a list for the key, creating the list if needed." + if not self.dict.has_key(key): self.dict[key]=[value] + else: self.dict[key].append(value) + def __getitem__(self, key): + "Return the list matching a key" + return self.dict[key] + +def PrintUsage(): + print """Pipermail %s +usage: pipermail [options] +options: -a URL : URL to other archives + -b URL : URL to archive information + -c file : name of configuration file (default: ~/.pmrc) + -d dir : directory where the output files will be placed + (default: archive/) + -l name : name of the output archive + -m file : name of input file + -s file : name where the archive state is stored + (default: <input file>+'.pipermail' + -u : Select 'update' mode + -v : verbose mode of operation + """ % (VERSION,) + sys.exit(0) + +# Compile various important regexp patterns +import regex, regsub +# Starting <html> directive +htmlpat=regex.compile('^[ \t]*<html>[ \t]*$') +# Ending </html> directive +nohtmlpat=regex.compile('^[ \t]*</html>[ \t]*$') +# Match quoted text +quotedpat=regex.compile('^[>|:]+') +# Parenthesized human name +paren_name_pat=regex.compile('.*\([(].*[)]\).*') +# Subject lines preceded with 'Re:' +REpat=regex.compile('[ \t]*[Rr][Ee][ \t]*:[ \t]*') +# Lines in the configuration file: set pm_XXX = <something> +cfg_line_pat=regex.compile('^[ \t]*[sS][eE][tT][ \t]*[Pp][Mm]_\([a-zA-Z0-9]*\)' + '[ \t]*=[ \t]*\(.*\)[ \t\n]*$') +# E-mail addresses and URLs in text +emailpat=regex.compile('\([-+,.a-zA-Z0-9]*@[-+.a-zA-Z0-9]*\)') +urlpat=regex.compile('\([a-zA-Z0-9]+://[^ \t\n]+\)') # URLs in text +# Blank lines +blankpat=regex.compile('^[ \t\n]*$') + +def ReadCfgFile(prefs): + import posixpath + try: + f=open(posixpath.expanduser(prefs['CONFIGFILE']), 'r') + except IOError, (num, msg): + if num==2: return + else: raise IOError, (num, msg) + line=0 + while(1): + L=f.readline() ; line=line+1 + if L=="": break + if string.strip(L)=="": continue # Skip blank lines + match=cfg_line_pat.match(L) + if match==-1: + print "Syntax error in line %i of %s" %(line, prefs['CONFIGFILE']) + print L + else: + varname, value=cfg_line_pat.group(1,2) + varname=string.upper(varname) + if not prefs.has_key(varname): + print ("Unknown variable name %s in line %i of %s" + %(varname, line, prefs['CONFIGFILE'])) + print L + else: + prefs[varname]=eval(value) + f.close() + +def ReadEnvironment(prefs): + import sys, os + for key in prefs.keys(): + envvar=string.upper('PM_'+key) + if os.environ.has_key(envvar): + if type(prefs[key])==type(''): prefs[key]=os.environ[envvar] + else: prefs[key]=string.atoi(os.environ[envvar]) + +def UpdateMsgHeaders(prefs, filename, L): + """Update the next/previous message information in a message header. +The message is scanned for <!--next--> and <!--endnext--> comments, and +new pointers are written. Otherwise, the text is simply copied without any processing.""" + pass + +def ProcessMsgBody(prefs, msg, filename, articles): + """Transform one mail message from plain text to HTML. +This involves writing an HTML header, scanning through the text looking +for <html></html> directives, e-mail addresses, and URLs, and +finishing off with a footer.""" + import cgi, posixpath + outputname=posixpath.join(prefs['DIR'], filename) + output=open(outputname, 'w') + os.chmod(outputname, prefs['FILEMODE']) + subject, email, poster, date, datestr, parent, id = articles[filename] + # JV + if not email: + email = '' + if not subject: + subject = '<No subject>' + if not poster: + poster = '*Unknown*' + if not datestr: + datestr = '' + output.write('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 3.0//EN">' + "<html><head><title>%s Mailing List: %s</title></head>" + "<body><h1>%s</h1>" + "%s (<i>%s</i>)<br><i>%s</i><p>" % + (prefs['LABEL'], cgi.escape(subject),cgi.escape(subject), + cgi.escape(poster),cgi.escape(email), + cgi.escape(datestr))) + output.write('<ul><li> <b>Messages sorted by:</b>' + '<a target="toc" href="date.html#1">[ date ]</a>' + '<a target="toc" href="thread.html#1">[ thread ]</a>' + '<a target="toc" href="subject.html#1">[ subject ]</a>' + '<a target="toc" href="author.html#1">[ author ]</a></ul>\n') + + html_mode=0 + if prefs['SHOWHR']: output.write('<hr>') + output.write('<p>') + if not prefs['SHOWHTML']: output.write('<pre>\n') + msg.rewindbody() # Seek to start of message body + quoted=-1 + while (1): + L=msg.fp.readline() + if L=="": break + if html_mode: + # If in HTML mode, check for ending tag; otherwise, we + # copy the line without any changes. + if nohtmlpat.match(L)==-1: + output.write(L) ; continue + else: + html_mode=0 + if not prefs['SHOWHTML']: output.write('<pre>\n') + continue + # Check for opening <html> tag + elif htmlpat.match(L)!=-1: + html_mode=1 + if not prefs['SHOWHTML']: output.write('</pre>\n') + continue + if prefs['SHOWHTML'] and prefs['IQUOTES']: + # Check for a line of quoted text and italicise it + # (We have to do this before escaping HTML special + # characters because '>' is commonly used.) + quoted=quotedpat.match(L) + if quoted!=-1: + L=cgi.escape(L[:quoted]) + '<i>' + cgi.escape(L[quoted:]) + '</i>' + # If we're flowing the message text together, quoted lines + # need explicit breaks, no matter what mode we're in. + if prefs['SHOWHTML']: L=L+'<br>' + else: L=cgi.escape(L) + else: L=cgi.escape(L) + + # Check for an e-mail address + L2="" ; i=emailpat.search(L) + while i!=-1: + length=len(emailpat.group(1)) + mailcmd=prefs['MAILCOMMAND'] % {'TO':L[i:i+length]} + L2=L2+'%s<A HREF="%s">%s</A>' % (L[:i], + mailcmd, L[i:i+length]) + L=L[i+length:] + i=emailpat.search(L) + L=L2+L ; L2=""; i=urlpat.search(L) + while i!=-1: + length=len(urlpat.group(1)) + L2=L2+'%s<A HREF="%s">%s</A>' % (L[:i], + L[i:i+length], L[i:i+length]) + L=L[i+length:] + i=urlpat.search(L) + L=L2+L + if prefs['SHOWHTML']: + while (L!="" and L[-1] in '\015\012'): L=L[:-1] + if prefs['SHOWBR']: + # We don't want to flow quoted passages + if quoted==-1: L=L+'<br>' + else: + # If we're not adding <br> to each line, we'll need to + # insert <p> markup on blank lines to separate paragraphs. + if blankpat.match(L)!=-1: L=L+'<p>' + L=L+'\n' + output.write(L) + + if not prefs['SHOWHTML'] and not html_mode: output.write('</pre>') + if prefs['SHOWHR']: output.write('<hr>') + output.write('<!--next-->\n<!--endnext-->\n</body></html>') + output.close() + +def WriteHTMLIndex(prefs, fp, L, articles, indexname): + """Process a list L into an HTML index, written to fp. +L is processed from left to right, and contains a list of 2-tuples; +an integer of 1 or more giving the depth of indentation, and +a list of strings which are used to reference the 'articles' +dictionary. Most of the time the lists contain only 1 element.""" + fp.write('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 3.0//EN">\n' + "<html><head><base target=display>" + "<title>%s Mailing List Archive by %s</title></head><body>\n" + % (prefs['LABEL'], indexname)) + fp.write('<H1><A name="start">%s Mailing List Archive by %s</A></H1>' + '<ul><li> <b><a target="toc" href="#end">Most recent messages</a></b>' + '<li> <b>Messages sorted by:</b>' + % (prefs['LABEL'], indexname)) + if indexname!='Date': + fp.write('<a target="toc" href="date.html#start">[ date ]</a>') + if indexname!='Subject': + fp.write('<a target="toc" href="subject.html#start">[ subject ]</a>') + if indexname!='Author': + fp.write('<a target="toc" href="author.html#start">[ author ]</a>') + if indexname!='Thread': + fp.write('<a target="toc" href="thread.html#start">[ thread ]</a>') + if prefs['ARCHIVES']!='NONE': + fp.write('<li> <b><a href="%s">Other mail archives</a></b>' % + (prefs['ARCHIVES'],)) +# This doesn't look professional. -- JV +# mailcmd=prefs['MAILCOMMAND'] % {'TO':'amk@magnet.com'} +# fp.write('</ul><p>Please inform <A HREF="%s">amk@magnet.com</a> if any of the messages are formatted incorrectly.' % (mailcmd,) ) + + fp.write("<p><b>Starting:</b> <i>%s</i><br>" + "<b>Ending:</b> <i>%s</i><br><b>Messages:</b> %i<p>" + % (prefs['firstDate'], prefs['endDate'], len(L)) ) + + # Write the index + level=1 + fp.write('<ul>\n') + for indent, keys in L: + if indent>level and indent<=prefs['THRDLEVELS']: + fp.write((indent-level)*'<ul>'+'\n') + if indent<level: fp.write((level-indent)*'</ul>'+'\n') + level=indent + for j in keys: + subj, email, poster, date, datestr, parent, id=articles[j] + # XXX Should we put a mailto URL in here? + fp.write('<li> <A HREF="%s"><b>%s</b></a> <i>%s</i>\n' % + (j, subj, poster) ) + for i in range(0, indent): fp.write('</ul>') + fp.write('<p>') + + # Write the footer + import time + now=time.asctime(time.localtime(time.time())) + +# JV -- Fixed a bug here. + if prefs['ARCHIVES'] <> 'NONE': + otherstr=('<li> <b><a href="%s">Other mail archives</a></b>' % + (prefs['ARCHIVES'],) ) + else: otherstr="" + fp.write('<a name="end"><b>Last message date:</b></a> <i>%s</i><br>' + '<b>Archived on:</b> <i>%s</i><p><ul>' + '<li> <b>Messages sorted by:</b>' + '<a target="toc" href="date.html#start">[ date ]</a>' + '<a target="toc" href="subject.html#start">[ subject ]</a>' + '<a target="toc" href="author.html#start">[ author ]</a>' + '<a target="toc" href="thread.html#start">[ thread ]</a>' + '%s</ul><p>' % (prefs['endDate'], now, otherstr)) + + fp.write('<p><hr><i>This archive was generated by ' +# JV Updated the URL. + '<A HREF="http://www.magnet.com/~amk/python/pipermail.html">' + 'Pipermail %s</A>.</i></body></html>' % (VERSION,)) + +# Set the hard-wired preferences first +# JV Changed the SHOWHTML pref default to 0 because 1 looks bad. +prefs={'CONFIGFILE':'~/.pmrc', 'MBOX':'mbox', + 'ARCHIVES': 'NONE', 'ABOUT':'NONE', 'REVERSE':0, + 'SHOWHEADERS':0, 'SHOWHTML':0, 'LABEL':"", + 'DIR':'archive', 'DIRMODE':0755, + 'FILEMODE':0644, 'OVERWRITE':0, 'VERBOSE':0, + 'THRDLEVELS':3, 'SHOWBR':0, 'IQUOTES':1, + 'SHOWHR':1, 'MAILCOMMAND':'mailto:%(TO)s', + 'INDEXFILE':'NONE' +} + +# Read the ~/.pmrc file +ReadCfgFile(prefs) +# Read environment variables +ReadEnvironment(prefs) + +# Parse command-line options +import getopt +options, params=getopt.getopt(sys.argv[1:], 'a:b:c:d:l:m:s:uipvxzh?') +for option, value in options: + if option=='-a': prefs['ARCHIVES']=value + if option=='-b': prefs['ABOUT']=value + if option=='-c': prefs['CONFIGFILE']=value + if option=='-d': prefs['DIR']=value +# if option=='-f': prefs.frames=1 + if option=='-i': prefs['MBOX']='-' + if option=='-l': prefs['LABEL']=value + if option=='-m': prefs['MBOX']=value + if option=='-s': prefs['INDEXFILE']=value + if option=='-p' or option=='-v': prefs['VERBOSE']=1 + if option=='-x': prefs['OVERWRITE']=1 + if option=='-z' or option=='-h' or option=='-?': PrintUsage() + +# Set up various variables +articles={} ; sequence=0 +for key in ['INDEXFILE', 'MBOX', 'CONFIGFILE', 'DIR']: + prefs[key]=posixpath.expanduser(prefs[key]) + +if prefs['INDEXFILE']=='NONE': + if prefs['MBOX']!='-': + prefs['INDEXFILE']=prefs['MBOX']+'.pipermail' + else: prefs['INDEXFILE']='mbox.pipermail' + +# Read an index file, if one can be found +if not prefs['OVERWRITE']: + # Look for a file contained pickled state + import pickle + try: + if prefs['VERBOSE']: + print 'Attempting to read index file', prefs['INDEXFILE'] + f=open(prefs['INDEXFILE'], 'r') + articles, sequence =pickle.load(f) + f.close() + except IOError: + if prefs['VERBOSE']: print 'No index file found.' + pass # Ignore errors + +# Open the input file +if prefs['MBOX']=='-': prefs['MBOX']=sys.stdin +else: + if prefs['VERBOSE']: print 'Opening input file', prefs['MBOX'] + prefs['MBOX']=open(prefs['MBOX'], 'r') + +# Create the destination directory; if it already exists, we don't care +try: + os.mkdir(prefs['DIR'], prefs['DIRMODE']) + if prefs['VERBOSE']: print 'Directory %s created'%(prefs['DIR'],) +except os.error, (errno, errmsg): pass + +# Create various data structures: +# msgids maps Message-IDs to filenames. +# roots maps Subject lines to (date, filename) tuples, and is used to +# identify the oldest article with a given subject line for threading. + +msgids={} ; roots={} +for i in articles.keys(): + subject, email, poster, date, datestr, parent, id =articles[i] + if id: msgids[id]=i + if not roots.has_key(subject) or roots[subject]<date: + roots[subject]=(date, i) + +# Start processing the index +import mailbox +mbox=mailbox.UnixMailbox(prefs['MBOX']) +while (1): + m=mbox.next() + if not m: break + + filename='%04i.html' % (sequence,) + if prefs['VERBOSE']: sys.stdout.write("Processing "+filename+"\n") + # The apparently redundant str() actually catches the case where + # m.getheader() returns None. + subj=str(m.getheader('Subject')) + # Remove any number of 'Re:' prefixes from the subject line + while (1): + i=REpat.match(subj) + if i!=-1: subj=subj[i:] + else: break + # Locate an e-mail address + L=m.getheader('From') + # JV: sometimes there is no From header, so use the one from unixfrom. + if not L: + try: + L = string.split(m.unixfrom)[1] + except: + L = "***Unknown***" + email=None + i=emailpat.search(L) + if i!=-1: + length=emailpat.match(L[i:]) + email=L[i:i+length] + # Remove e-mail addresses inside angle brackets + poster=str(regsub.gsub('<.*>', '', L)) + # Check if there's a name in parentheses + i=paren_name_pat.match(poster) + if i!=-1: poster=paren_name_pat.group(1)[1:-1] + datestr=m.getheader('Date') + # JV -- Hacks to make the getdate work. + # These hacks might skew the post time a bit. + # Crude, but so far, effective. + words = string.split(datestr) + if ((len(words[-1]) == 4) and (len(words) == 5) + and (words[-1][:-1] == '199')): + try: + date = time.mktime(rfc822.parsedate('%s, %s %s %s %s' % + (words[0], words[2], words[1], + words[4], words[3]))) + except: + date = time.mktime(m.getdate('Date')) # Odd + elif len(words) > 4 and words[4][-1] == ',': + try: + date = time.mktime(rfc822.parsedate('%s, %s %s %s %s' % + (words[0], words[1], words[2], + words[3], words[4][:-1]))) + except: + date = time.mktime(m.getdate('Date')) # Hmm + else: + try: + date=time.mktime(m.getdate('Date')) + except: + print 'Error getting date!' + print 'Subject = ', m.getheader('subject') + print 'Date = ', m.getheader('date') + + id=m.getheader('Message-Id') + if id: id=id[1:-1] ; msgids[id]=filename + parent=None + in_reply_to=m.getheader('In-Reply-To') + if in_reply_to: + in_reply_to=in_reply_to[1:-1] + if msgids.has_key(in_reply_to): parent=msgids[in_reply_to] + elif roots.has_key(subj) and roots[subj][0]<date: + parent=roots[subj][1] + else: roots[subj]=(date,filename) + + articles[filename]=(subj, email, poster, date, datestr, parent, id) + ProcessMsgBody(prefs, m, filename, articles) + sequence=sequence+1 +prefs['MBOX'].close() + +if prefs['VERBOSE']: sys.stdout.write("Writing date index\n") +import time +indexname=posixpath.join(prefs['DIR'], 'date.html') +f=open(indexname, 'w') ; os.chmod(indexname, prefs['FILEMODE']) +dates=ListDict() +for i in articles.keys(): + subject, email, poster, date, datestr, parent, id=articles[i] + dates[date]=i +L=dates.keys() ; L.sort() +if prefs['REVERSE']: L.reverse() +prefs['firstDate']=time.asctime(time.localtime(L[0])) +prefs['endDate']=time.asctime(time.localtime(L[-1])) +L=map(lambda key, s=dates: (1,s[key]), L) +WriteHTMLIndex(prefs, f, L, articles, 'Date') +f.close() ; del dates, L + +if prefs['VERBOSE']: sys.stdout.write("Writing thread index\n") +indexname=posixpath.join(prefs['DIR'], 'thread.html') +f=open(indexname, 'w') ; os.chmod(indexname, prefs['FILEMODE']) +def DFS(p, N=None, depth=0, prefs=prefs): + set=filter(lambda x, N=N, p=p: p[x][1]==N, p.keys()) + set=map(lambda x, a=articles: (articles[x][3],x), set) + set.sort() + if prefs['REVERSE']: set.reverse() + set=map(lambda x: x[1], set) + if len(set)==0: return [(depth, [N])] + else: + L=[] + for i in set: + L=L+DFS(p, i, depth+1) + return [(depth,[N])]+L +parents={} +for i in articles.keys(): + subject, email, poster, date, datestr, parent, id=articles[i] + parents[i]=(date, parent) +L=DFS(parents)[1:] +WriteHTMLIndex(prefs, f, L, articles, 'Thread') +f.close() ; del L, parents + +if prefs['VERBOSE']: sys.stdout.write("Writing subject index\n") +indexname=posixpath.join(prefs['DIR'], 'subject.html') +f=open(indexname, 'w') ; os.chmod(indexname, prefs['FILEMODE']) +subjects=ListDict() +for i in articles.keys(): + subject, email, poster, date, datestr, parent, id=articles[i] + subjects[(subject, date)]=i +L=subjects.keys() ; L.sort() ; L=map(lambda key, s=subjects: (1, s[key]), L) +WriteHTMLIndex(prefs, f, L, articles, 'Subject') +f.close() ; del subjects, L + +if prefs['VERBOSE']: sys.stdout.write("Writing author index\n") +indexname=posixpath.join(prefs['DIR'], 'author.html') +f=open(indexname, 'w') ; os.chmod(indexname, prefs['FILEMODE']) +authors=ListDict() +for i in articles.keys(): + v=articles[i] + authors[(v[2],v[3])]=i +L=authors.keys() ; L.sort() ; L=map(lambda key, s=authors: (1,s[key]), L) +WriteHTMLIndex(prefs, f, L, articles, 'Author') +f.close() ; del authors, L + +if prefs['VERBOSE']: sys.stdout.write("Writing framed index\n") +f=open(posixpath.join(prefs['DIR'], 'blank.html'), 'w') +f.write("<html></html>") ; f.close() +# JV changed... +f=open(posixpath.join(prefs['DIR'], mm_cfg.HOME_PAGE), 'w') +f.write("""<html><head><title>%s Pipermail Archive</title> +<frameset cols="*, 60%%"> +<FRAME SRC="thread.html" NAME=toc> +<FRAME SRC="blank.html" NAME=display> +</frameset></head> +<body><noframes> +To access the %s Pipermail Archive, choose one of the following links: +<p> +Messages sorted by <a target="toc" href="date.html#start">[ date ] </a> +<a target="toc" href="subject.html#start">[ subject ]</a> +<a target="toc" href="author.html#start">[ author ]</a> +<a target="toc" href="thread.html#start">[ thread ]</a> +</ol> +</noframes> +</body></html> +""" % (prefs['LABEL'],prefs['LABEL']) ) + + +import pickle +if prefs['VERBOSE']: print 'Writing index file', prefs['INDEXFILE'] +f=open(prefs['INDEXFILE'], 'w') +pickle.dump( (articles, sequence), f ) +f.close() @@ -0,0 +1,86 @@ +There's still much that I'd like to redo or add. I'm not incredibly +proud of the code, and it's a bit embarassing to be sharing it at this +point. The weakest points in my mind are probably the cgi scripts and +html formatting. Unfortunately, I won't soon have the time to devote +to doing it better. + +Initial version of Mailman (v. 0.9) written by John Viega Dec 12-15 1996 +See file DONE for info on changes since v. 0.9 + +Features: + o Most standard mailing list features, including: + moderation, mail based commands, digests, etc... + o An extensive web interface customizable on a per-list basis. + o Web based list administration interface for *all* admin-type tasks. + o Automatic web based hypermail-style archives (using pipermail) + o Smart bounce detection and correction + o Fast bulk mailing + o Multiple list owners and moderators + o Optional MIME-Compliant digests + o Nice about which machine you subscribed from if you're from the + right domain. + +Requirements: + You must be root on a machine running a mail transport program that + uses an /etc/aliases file, and has a sendmail executable (smail + should be OK). Eventually I'd really like to support qmail, + but currently don't. + The machine really needs to have a web server in order to configure lists. + +Install: + o Make sure Python is installed as /usr/local/bin/python (currently + hardcoded -- this obviously needs to change, and should be + handled by a config program) + o As root, add a mailman user + o As mailman, copy the mailman dir to /home/mailman. Currently the + system requires the source be located there. + o As mailman, Make /home/mailman/public_html and /home/mailman/cgi-bin + o As root, Configure your web server to give /home/mailman/cgi-bin + perms to run cgi in the cgi-bin dir, and restart the web server. + The line you should add should look something like: + Exec /mailman/* /home/mailman/cgi-bin/* + or: + ScriptAlias /mailman/ /home/mailman/cgi-bin/ + + (Or whatever is equivolent for your web server) + o As root, edit /home/mailman/mailman/src/*.c and edit the UID and + GID const lines as appropriate. + o As root, cd to /home/mailman/mailman/src and type: make + o As root, Add mailman as a trusted mail user. This is usually done + by adding: Tmailman to /etc/sendmail.cf under the line: Troot + o As root, Restart sendmail. + o As mailman, cd to /home/mailman/mailman/cron, and add crontab.in + as your crontab. (probably done w/: crontab crontab.in) + o Edit modules/mm_cfg and change the first 3 macros as appropriate. + +Adding a new list: + o Run the program bin/newlist + + +Troubleshooting: + +If the web pages hang: + + CERN web servers might leave python's running, and in some + cases might hang the cgi completely. In that case, switch to + Apache. + +If from the web you get "document contains no data": +If mail isn't getting delivered: + The cgi wrappers are failing. Either a UID is wrong, or your + web server / mailer has a non-standard name. + +Some notes about the code (Developers notes): + How to add a new user option: + + 1) Add a flag to mm_cfg + 2) Add an entry to mm_html name mapping. + 3) Add replacements lines to the cgi/options script. + 4) cgi/handle_opts: make SetUserOption calls. + 5) Add to 2 data structs at top of mm_mailcmd + 6) Add to mm_mailcmd help + 7) Update templates + 8) Use your option wherever appropriate... + + + @@ -0,0 +1,83 @@ +For v .96: +o Add more bounce patterns.... +o Confirmation when you change anything... +o Fix what new list mail has to say. +o Link from subscribe to options +o Edit posts from admindb page +o Make who, info, etc... take list as an optional argument. +o subject in caps +o headers on approve should die +o link to other lists, have a global page of lists on this server... +o ordering of post requests +o first time bounce on stale addr problem. +o link from subscribe page to options page +For the near future: +o Make these filter functions live together in self. +o Lower case all cmds in mailcmd +o Does admin password work for mail cmds? +o Occasionally remove stale bounce entries +o lower case all names? +o Remove the bounce log. +o Implement the option to mail admin instead of auto-removing... +o Send mail to the guy when you forcefully unsubscribe him... (IE, through bounce management) +o Automatically insert a crontab entry to update the archives of a new list. +o mkdigest command for cron, plus over the web button. +o Search engine for archives +o Month annotations? +o Logging -- remove all tmp logs. +o Generic error template +o Go over code looking for dead functions / code (esp mm_html funcs) +o Mailman web pages / documentation +o The config program +o remove/fix valid_parent check in wrappers +o Catch common commands in mail... +o md5 checksums to prevent mail loops (how will this work?) +o For archives, perhaps switch to every n months? +o Receive Non-Mail Files +o If you don't crush by default, you get 2 subject lines... +o Clean up the code. + +For the Mailman home page: +o What are digests? +o What does option XXX mean? +o How about removing a list? +o Document what tags you can use on each web page when editing html. + -- Add links to the right pages. + +The Config program: +o Remember to: + -- trusted user the mailman (how about smail users?) + -- Mail to mailman should go to server owner + -- add Cw entries (smail users?) +o chmod o+x all these scripts in install. +o Hook up cron scripts as part of install. +o Need to make sure that all paths used in code and the #! lines get updated + by the config process. +o Add mailman and mailman-owner users. +o Notification on subscribes / unsubscribes + + +#################################### + +Potential stuff for future versions: +o Configurable logging? +o Make it be a client-server thing, instead of reloading list info every time + some program wants to perform an operation on a list. +o Make the web page / list man stuff machine independant (Will require the + client/server deal). +o Make the tag stuff accept arguments. +o New list -- prevent posting option +o precidence header +o Administrivia a la majordomo? +o Approval not over the web. (A by-mail interface to the admin stuff) +o Newsgroups gateway +o Global mailing list services: + -- bit saying whether we've notified the main server. + -- support for periodic confirmations. + -- pass on stats. + + + + + + diff --git a/bin/addaliases b/bin/addaliases Binary files differnew file mode 100755 index 000000000..537f077d9 --- /dev/null +++ b/bin/addaliases diff --git a/bin/digest_arch b/bin/digest_arch new file mode 100755 index 000000000..06ce28e40 --- /dev/null +++ b/bin/digest_arch @@ -0,0 +1,130 @@ +#!/usr/local/bin/python +# +# This program shouldn't be attempted by people who don't understand Python. +# I wrote it to build archives for a few lists I'd been running for a long +# time under majordomo. +# +# Convert majordomo digests all stored in one directory into mailbox +# format. Note that the digests correct order in the dir should be +# alphabetical order. +# +# The output file is ARCHIVE.ME in the same directory the digests are in. +# Run this program before you transfer the majordomo list. +# +# To get the output file archived, create the list under mailman, +# run this script, and then do the following: +# +# cat ARCHIVE.ME >> ~mailman/mailman/lists/mylist/archived.mail +# +# You also need to adjust the variable: +# NUM_LINES_TO_STRIP_FROM_TOP + +import string, sys, os + +NUM_LINES_TO_STRIP_FROM_TOP = 11 + + +def setfromaddr(txt): + global From + words = string.split(txt)[1:] + if len(words) == 1: + From = words[0] + return +# This next line might be the source of an error if a digest has a +# null message in it. If it does, remove that null message. + if words[-1][0] == '<': + From = words[-1][1:-1] + return + else: + From = words[0] + return + +days_of_week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] +last_day = 'Sun' # See comment below ;-) +last_dow = 0 + +def setdateinfo(s): + global dateinfo + global last_day + global last_dow + words = string.split(s) + if words[1][-1] <> ',': + day, mon, year, time = words[1], words[2], words[3], words[4] + +# This is a quick hack that assumes someone posted every day. I +# didn't make it more robust, because that's a lot more effort for +# something few people will ever notice anyway. + + if day == last_day: + dow = days_of_week[last_dow] + else: + dow = days_of_week[(last_dow + 1) % 7] + else: + dow, day, mon, year, time = words[1][:3], words[2], words[3], words[4], words[5] + if len(day) == 1: + day = '0' + day + + last_day = day + last_dow = days_of_week.index(dow) + + if len(year) == 2: + year = '19' + year + outfile.write("From %s %s %s %s %s %s\n" % (From, dow, mon, day, time, year)) +# print "From %s %s %s %s %s %s" % (From, dow, mon, day, time, year) + +def header(): + global lines, curline, msgtextstart + msgtextstart = curline + 2 + setfromaddr(lines[curline + 2]) + setdateinfo(lines[curline + 3]) + curline = curline + 4 + +def text(): + global lines, curline, msgtextend + while lines[curline] <> "------------------------------\n": + curline = curline + 1 + msgtextend = curline - 1 + +def output(): + for i in range(msgtextstart, msgtextend): + if lines[i][:5] == 'From ': + outfile.write('>') + outfile.write(lines[i]) + +def msg(): + global curline + header() + curline = curline + 2 #skip subject and blank line + text() + output() + +digest_dir = sys.argv[1] + +infiles = os.listdir(digest_dir) +infiles.sort() +try: + infiles.remove('ARCHIVE_ME') +except: + pass +outfile = open(os.path.join(digest_dir, 'ARCHIVE_ME'), 'w') + +for filename in infiles: + infilename = os.path.join(digest_dir, filename) + infile = open(infilename, 'r') + print infilename + lines = infile.readlines() + curline = NUM_LINES_TO_STRIP_FROM_TOP + numlines = len(lines) + msgtextstart = None + msgtextend = None + From = None + dateinfo = None + subject = None + while 1: + # could check lines[curline] == '------------------------------\n' + # but that should always be true. + if (lines[curline+1] == '\n' and lines[curline+2][:6] == 'End of' + and lines[curline+3][:6] == '******'): + break + msg() + infile.close() diff --git a/bin/populate_new_list b/bin/populate_new_list new file mode 100755 index 000000000..0349bf135 --- /dev/null +++ b/bin/populate_new_list @@ -0,0 +1,62 @@ +#!/usr/local/bin/python +# +# argv[1] should be the name of the list. +# argv[2] should be the list of non-digested users. +# argv[3] should be the list of digested users. + +# Make sure that the list of email addresses doesn't contain any comments, +# like majordomo may throw in. For now, you just have to remove them manually. + +import sys, os, crypt, string + +sys.path.append('/home/mailman/mailman/modules') + +import maillist, mm_utils, mm_message, mm_cfg + + + +def GetRandomPassword(): + return "%s%s" % (mm_utils.GetRandomSeed(), mm_utils.GetRandomSeed()) + + +if len(sys.argv) <> 4: + print 'Usage: populate_new_list <list name> <non-digest-members-file> <digest-members-file>' + sys.exit(0) + +try: + list = maillist.MailList(sys.argv[1]) +except: + print 'run newlist first...' + sys.exit(0) + + +try: + non_digest_members = string.split(open(sys.argv[2]).read(), '\n') +except: + non_digest_members = [] + print 'file for non-digest members could not be opened. Ignoring.' +try: + digest_members = string.split(open(sys.argv[3]).read(), '\n') +except: + digest_members = [] + print 'file for digest members could not be opened. Ignoring.' + +def FormatMembers(mbrs): + def NotNull(str): + return str + return filter(NotNull, map(string.strip, mbrs)) + +non_digest_members = FormatMembers(non_digest_members) +digest_members = FormatMembers(digest_members) + + +for member in non_digest_members: + pw = GetRandomPassword() + list.ApprovedAddMember(member, pw, 0) + +for member in digest_members: + pw = GetRandomPassword() + list.ApprovedAddMember(member, pw, 1) + + + diff --git a/bin/subscribe_enmasse b/bin/subscribe_enmasse new file mode 100755 index 000000000..0e1d11f21 --- /dev/null +++ b/bin/subscribe_enmasse @@ -0,0 +1,13 @@ +#!/usr/local/bin/python +# This program gets called when the list admin posts a list of names +# to be subscribed on the admin page. This is here because mailman +# needs to fork this off and make it do the work for the following +# reasons: +# +# 1) We need to fork so the web browser can return in a reasonable time +# 2) We need to run a ton of mailman processes to avoid holding the +# lock for an unreasonable amount of time. + +import sys +names = sys.stdin.read() +print names diff --git a/filters/bowa-strip b/filters/bowa-strip new file mode 100755 index 000000000..1985a0697 --- /dev/null +++ b/filters/bowa-strip @@ -0,0 +1,17 @@ +#!/usr/local/bin/python + +import sys, __main__, string + +input = sys.stdin.readlines() + +if input[-4] == '*********************Reminder**********************\n': + input[-5] = '' + input[-4] = '' + input[-3] = '' + input[-2] = '' + input[-1] = '' + +__main__.mailman_text = string.join(input,'') + + + diff --git a/modules/aliases.py b/modules/aliases.py new file mode 100644 index 000000000..8764912a8 --- /dev/null +++ b/modules/aliases.py @@ -0,0 +1,41 @@ +# This is mailman's interface to the alias database. + +# TODO: + +# Write a wrapper program w/ root uid that allows the mailman user +# only to update the alias database. + +import string +_file = open('/etc/aliases', 'r') +_lines = _file.readlines() +aliases = {} +_cur_line = None + +def _AddAlias(line): + line = string.strip(line) + if not line: + return + colon_index = string.find(line, ":") + if colon_index < 1: + raise "SyntaxError", "Malformed /etc/aliases file" + alias = string.lower(string.strip(line[:colon_index])) + rest = string.split(line[colon_index+1:], ",") + rest = map(string.strip, rest) + aliases[alias] = rest + +for _line in _lines: + if _line[0] == '#': + continue + if _line[0] == ' ' or _line[0] == '\t': + _cur_line = _cur_line + _line + continue + if _cur_line: + _AddAlias(_curline) + _cur_line = _line + +def GetAlias(str): + str = string.lower(str) + if not aliases.has_key(str): + raise KeyError, "No such alias" + return aliases[str] + diff --git a/modules/mm_err.py b/modules/mm_err.py new file mode 100644 index 000000000..8747b3e60 --- /dev/null +++ b/modules/mm_err.py @@ -0,0 +1,16 @@ +MMBadEmailError = "MMBadEmailError" +MMMustDigestError = "MMMustDigestError" +MMCantDigestError = "MMCantDigestError" +MMNotAMemberError = "MMNotAMemberError" +MMListNotReady = "MMListNotReady" +MMNoSuchUserError = "MMNoSuchUserError" +MMBadPasswordError = "MMBadPasswordError" +MMNeedApproval = "MMNeedApproval" +MMHostileAddress = "MMHostileAddress" +MMAlreadyAMember = "MMAlreadyAMember" +MMPasswordsMustMatch = "MMPasswordsMustMatch" +MMAlreadyDigested = "MMAlreadyDigested" +MMAlreadyUndigested = "MMAlreadyUndigested" +MMBadRequestId = "MMBadRequestId" +MMWebSubscribeRequiresConfirmation = "MMWebSubscribeRequiresConfirmation" + diff --git a/modules/mm_mbox.py b/modules/mm_mbox.py new file mode 100644 index 000000000..503ad9e15 --- /dev/null +++ b/modules/mm_mbox.py @@ -0,0 +1,16 @@ +import mailbox + +class Mailbox(mailbox.UnixMailbox): + # msg should be an rfc822 message or a subclass. + def AppendMessage(self, msg): + # seek to the last char of the mailbox + self.fp.seek(1,2) + if self.fp.read(1) <> '\n': + self.fp.write('\n') + self.fp.write(msg.unixfrom) + for line in msg.headers: + self.fp.write(line) + if msg.body[0] <> '\n': + self.fp.write('\n') + self.fp.write(msg.body) + diff --git a/modules/mm_message.py b/modules/mm_message.py new file mode 100644 index 000000000..21246dfb0 --- /dev/null +++ b/modules/mm_message.py @@ -0,0 +1,150 @@ +import sys +import rfc822, string, time + + +# A utility function 2 of these classes use: +def AddBackNewline(str): + return str + '\n' + + +# If we're trying to create a message object from text, we need to pass +# a file object to rfc822.Message to get it to do its magic. Well, +# to avoid writing text out to a file, and having it read back in, +# here we define a class that will fool rfc822 into thinking it's a non-seekable +# message. +# The only method rfc822.Message ever calls on a non-seekable file is +# readline. It doesn't use the optional arg to readline, either. +# In my subclasses, I use the read() method, and might just use readlines() +# someday. +# +# It might be useful to expand this into a full blown fully functional class. + +class FakeFile: + def __init__(self, text): + self.lines = map(AddBackNewline, string.split(text, '\n')) + self.curline = 0 + self.lastline = len(self.lines) - 1 + def readline(self): + if self.curline > self.lastline: + return '' + self.curline = self.curline + 1 + return self.lines[self.curline - 1] + def read(self): + startline = self.curline + self.curline = self.lastline + 1 + return string.join(self.lines[startline:], '') + def readlines(self): + startline = self.curline + self.curline = self.lastline + 1 + return self.lines[startline:] + + +# We know the message is gonna come in on stdin or from text for our purposes. +class IncomingMessage(rfc822.Message): + def __init__(self, text=None): + if not text: + rfc822.Message.__init__(self, sys.stdin, 0) + self.body = self.fp.read() + else: + rfc822.Message.__init__(self, FakeFile(text), 0) + self.body = self.fp.read() + + def GetSender(self): + # Look for a Sender field. + sender = self.getheader('sender') + if sender: + realname, mail_address = self.getaddr('sender') + else: + try: + realname, mail_address = self.getaddr('from') + except: + # The unix from line is all we have left... + if self.unixfrom: + return string.lower(string.split(self.unixfrom)[1]) + + return string.lower(mail_address) + + def GetSenderName(self): + real_name, mail_addr = self.getaddr('from') + if not real_name: + return self.GetSender() + return real_name + + def SetHeader(self, name, value, crush_duplicates=1): + # Well, we crush dups in the dict no matter what... + name = "%s%s" % (name[0], name[1:]) + self.dict[string.lower(name)] = value + if value[-1] <> '\n': + value = value + '\n' + + if not crush_duplicates: + self.headers.append('%s: %s' % (name, value)) + return + for i in range(len(self.headers)): + if (string.lower(self.headers[i][:len(name)+1]) == + string.lower(name) + ':'): + self.headers[i] = '%s: %s' % (name, value) + +# This is a simplistic class. It could do multi-line headers etc... +# But it doesn't because I don't need that for this app. +class OutgoingMessage: + def __init__(self, headers=None, body='', sender=None): + self.cached_headers = {} + if headers: + self.SetHeaders(headers) + else: + self.headers = [] + self.body = body + self.sender = sender + + def SetHeaders(self, headers): + self.headers = map(AddBackNewline, string.split(headers, '\n')) + + def CacheHeaders(header, s=self): + i = string.find(header, ':') + s.cached_headers[string.lower(string.strip(header[:i]))] = \ + header[i+2:] + map(CacheHeaders, self.headers) + + def SetHeader(self, header, value, crush_duplicates=1): + if value[-1] <> '\n': + value = value + '\n' + if crush_duplicates: + # Run through the list and make sure the header isn't already there. + remove_these = [] + for item in self.headers: + f = string.find(item, ':') + if string.lower(item[:f]) == string.lower(header): + remove_these.append(item) + for item in remove_these: + self.headers.remove(item) + del remove_these + self.headers.append('%s%s: %s' % (string.upper(header[0]), + string.lower(header[1:]), + value)) + self.cached_headers[string.lower(header)] = value + + def SetBody(self, body): + self.body = body + + def AppendToBody(self, text): + self.body = self.body + text + + def SetSender(self, sender, set_from=1): + self.sender = sender + if not self.getheader('from') and set_from: + self.SetHeader('from', sender) + + def SetDate(self, date=time.ctime(time.time())): + self.SetHeader('date', date) + + def GetSender(self): + return self.sender + +# Lower case the name to give it the same UI as IncomingMessage +# inherits from rfc822 + def getheader(self, str): + str = string.lower(str) + if not self.cached_headers.has_key(str): + return None + return self.cached_headers[str] diff --git a/modules/mm_security.py b/modules/mm_security.py new file mode 100644 index 000000000..83e27d009 --- /dev/null +++ b/modules/mm_security.py @@ -0,0 +1,59 @@ +import crypt, types, string, os +import mm_err, mm_utils, mm_cfg + +class SecurityManager: + def SetSiteAdminPassword(self, pw): + old = os.umask(0700) + f = open(os.path.join(mm_cfg.MAILMAN_DIR, "adm.pw"), "w+") + f.write(crypt.crypt(pw, mm_utils.GetRandomSeed())) + f.close() + os.umask(old) + + def CheckSiteAdminPassword(self, str): + try: + f = open(os.path.join(mm_cfg.MAILMAN_DIR, "adm.pw"), "r+") + pw = f.read() + f.close() + return crypt.crypt(str, pw) == pw + # There probably is no site admin password if there was an exception + except: + return 0 + + def InitVars(self, crypted_password): + # Configurable, however, we don't pass this back in GetConfigInfo + # because it's a special case as it requires confirmation to change. + self.password = crypted_password + + # Non configurable + self.passwords = {} + + def ValidAdminPassword(self, pw): + if self.CheckSiteAdminPassword(pw): + return 1 + return ((type(pw) == types.StringType) and + (crypt.crypt(pw, self.password) == self.password)) + + def ConfirmAdminPassword(self, pw): + if(not self.ValidAdminPassword(pw)): + raise mm_err.MMBadPasswordError + return 1 + + def ConfirmUserPassword(self, user, pw): + if self.ValidAdminPassword(pw): + return 1 + if not user in self.members and not user in self.digest_members: + user = self.FindUser(user) + if string.lower(pw) <> string.lower(self.passwords[user]): + raise mm_err.MMBadPasswordError + return 1 + + def ChangeUserPassword(self, user, newpw, confirm): + self.IsListInitialized() + addr = self.FindUser(user) + if not addr: + raise mm_err.MMNotAMemberError + if newpw <> confirm: + raise mm_err.MMPasswordsMustMatch + self.passwords[addr] = newpw + self.Save() + diff --git a/modules/nohup.out b/modules/nohup.out new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/modules/nohup.out diff --git a/modules/pipermail.py b/modules/pipermail.py new file mode 100644 index 000000000..ab92a7e29 --- /dev/null +++ b/modules/pipermail.py @@ -0,0 +1,552 @@ +#!/usr/local/bin/python +# Hey Emacs, this is -*-Python-*- code! +# +# Pipermail 0.0.2-mm +# +# Some minor mods have been made for use with the Mailman mailing list manager. +# All changes will have JV by them. +# +# (C) Copyright 1996, A.M. Kuchling (amk@magnet.com) +# Home page at http://amarok.magnet.com/python/pipermail.html +# +# HTML code for frames courtesy of Scott Hassan (hassan@cs.stanford.edu) +# +# TODO: +# * Prev. article, next. article pointers in each article +# * I suspect there may be problems with rfc822.py's getdate() method; +# take a look at the threads "Greenaway and the net (fwd)" or +# "Pillow Book pictures". To be looked into... +# * Anything else Hypermail can do that we can't? +# * General code cleanups +# * Profiling & optimization +# * Should there be an option to enable/disable frames? +# * Like any truly useful program, Pipermail should have an ILU interface. +# * There's now an option to keep from preserving line breaks, +# so paragraphs in messages would be reflowed by the browser. +# Unfortunately, this mangles .sigs horribly, and pipermail doesn't yet +# put in paragraph breaks. Putting in the breaks will only require a +# half hour or so; I have no clue as to how to preserve .sigs. +# * Outside URLs shouldn't appear in the display frame. How to fix? +# + +VERSION = "0.0.2.mm" + +import posixpath, time, os, string, sys, rfc822 + +# JV -- to get HOME_PAGE +import mm_cfg + +class ListDict: + def __init__(self): self.dict={} + def keys(self): return self.dict.keys() + def __setitem__(self, key, value): + "Add the value to a list for the key, creating the list if needed." + if not self.dict.has_key(key): self.dict[key]=[value] + else: self.dict[key].append(value) + def __getitem__(self, key): + "Return the list matching a key" + return self.dict[key] + +def PrintUsage(): + print """Pipermail %s +usage: pipermail [options] +options: -a URL : URL to other archives + -b URL : URL to archive information + -c file : name of configuration file (default: ~/.pmrc) + -d dir : directory where the output files will be placed + (default: archive/) + -l name : name of the output archive + -m file : name of input file + -s file : name where the archive state is stored + (default: <input file>+'.pipermail' + -u : Select 'update' mode + -v : verbose mode of operation + """ % (VERSION,) + sys.exit(0) + +# Compile various important regexp patterns +import regex, regsub +# Starting <html> directive +htmlpat=regex.compile('^[ \t]*<html>[ \t]*$') +# Ending </html> directive +nohtmlpat=regex.compile('^[ \t]*</html>[ \t]*$') +# Match quoted text +quotedpat=regex.compile('^[>|:]+') +# Parenthesized human name +paren_name_pat=regex.compile('.*\([(].*[)]\).*') +# Subject lines preceded with 'Re:' +REpat=regex.compile('[ \t]*[Rr][Ee][ \t]*:[ \t]*') +# Lines in the configuration file: set pm_XXX = <something> +cfg_line_pat=regex.compile('^[ \t]*[sS][eE][tT][ \t]*[Pp][Mm]_\([a-zA-Z0-9]*\)' + '[ \t]*=[ \t]*\(.*\)[ \t\n]*$') +# E-mail addresses and URLs in text +emailpat=regex.compile('\([-+,.a-zA-Z0-9]*@[-+.a-zA-Z0-9]*\)') +urlpat=regex.compile('\([a-zA-Z0-9]+://[^ \t\n]+\)') # URLs in text +# Blank lines +blankpat=regex.compile('^[ \t\n]*$') + +def ReadCfgFile(prefs): + import posixpath + try: + f=open(posixpath.expanduser(prefs['CONFIGFILE']), 'r') + except IOError, (num, msg): + if num==2: return + else: raise IOError, (num, msg) + line=0 + while(1): + L=f.readline() ; line=line+1 + if L=="": break + if string.strip(L)=="": continue # Skip blank lines + match=cfg_line_pat.match(L) + if match==-1: + print "Syntax error in line %i of %s" %(line, prefs['CONFIGFILE']) + print L + else: + varname, value=cfg_line_pat.group(1,2) + varname=string.upper(varname) + if not prefs.has_key(varname): + print ("Unknown variable name %s in line %i of %s" + %(varname, line, prefs['CONFIGFILE'])) + print L + else: + prefs[varname]=eval(value) + f.close() + +def ReadEnvironment(prefs): + import sys, os + for key in prefs.keys(): + envvar=string.upper('PM_'+key) + if os.environ.has_key(envvar): + if type(prefs[key])==type(''): prefs[key]=os.environ[envvar] + else: prefs[key]=string.atoi(os.environ[envvar]) + +def UpdateMsgHeaders(prefs, filename, L): + """Update the next/previous message information in a message header. +The message is scanned for <!--next--> and <!--endnext--> comments, and +new pointers are written. Otherwise, the text is simply copied without any processing.""" + pass + +def ProcessMsgBody(prefs, msg, filename, articles): + """Transform one mail message from plain text to HTML. +This involves writing an HTML header, scanning through the text looking +for <html></html> directives, e-mail addresses, and URLs, and +finishing off with a footer.""" + import cgi, posixpath + outputname=posixpath.join(prefs['DIR'], filename) + output=open(outputname, 'w') + os.chmod(outputname, prefs['FILEMODE']) + subject, email, poster, date, datestr, parent, id = articles[filename] + # JV + if not email: + email = '' + if not subject: + subject = '<No subject>' + if not poster: + poster = '*Unknown*' + if not datestr: + datestr = '' + output.write('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 3.0//EN">' + "<html><head><title>%s Mailing List: %s</title></head>" + "<body><h1>%s</h1>" + "%s (<i>%s</i>)<br><i>%s</i><p>" % + (prefs['LABEL'], cgi.escape(subject),cgi.escape(subject), + cgi.escape(poster),cgi.escape(email), + cgi.escape(datestr))) + output.write('<ul><li> <b>Messages sorted by:</b>' + '<a target="toc" href="date.html#1">[ date ]</a>' + '<a target="toc" href="thread.html#1">[ thread ]</a>' + '<a target="toc" href="subject.html#1">[ subject ]</a>' + '<a target="toc" href="author.html#1">[ author ]</a></ul>\n') + + html_mode=0 + if prefs['SHOWHR']: output.write('<hr>') + output.write('<p>') + if not prefs['SHOWHTML']: output.write('<pre>\n') + msg.rewindbody() # Seek to start of message body + quoted=-1 + while (1): + L=msg.fp.readline() + if L=="": break + if html_mode: + # If in HTML mode, check for ending tag; otherwise, we + # copy the line without any changes. + if nohtmlpat.match(L)==-1: + output.write(L) ; continue + else: + html_mode=0 + if not prefs['SHOWHTML']: output.write('<pre>\n') + continue + # Check for opening <html> tag + elif htmlpat.match(L)!=-1: + html_mode=1 + if not prefs['SHOWHTML']: output.write('</pre>\n') + continue + if prefs['SHOWHTML'] and prefs['IQUOTES']: + # Check for a line of quoted text and italicise it + # (We have to do this before escaping HTML special + # characters because '>' is commonly used.) + quoted=quotedpat.match(L) + if quoted!=-1: + L=cgi.escape(L[:quoted]) + '<i>' + cgi.escape(L[quoted:]) + '</i>' + # If we're flowing the message text together, quoted lines + # need explicit breaks, no matter what mode we're in. + if prefs['SHOWHTML']: L=L+'<br>' + else: L=cgi.escape(L) + else: L=cgi.escape(L) + + # Check for an e-mail address + L2="" ; i=emailpat.search(L) + while i!=-1: + length=len(emailpat.group(1)) + mailcmd=prefs['MAILCOMMAND'] % {'TO':L[i:i+length]} + L2=L2+'%s<A HREF="%s">%s</A>' % (L[:i], + mailcmd, L[i:i+length]) + L=L[i+length:] + i=emailpat.search(L) + L=L2+L ; L2=""; i=urlpat.search(L) + while i!=-1: + length=len(urlpat.group(1)) + L2=L2+'%s<A HREF="%s">%s</A>' % (L[:i], + L[i:i+length], L[i:i+length]) + L=L[i+length:] + i=urlpat.search(L) + L=L2+L + if prefs['SHOWHTML']: + while (L!="" and L[-1] in '\015\012'): L=L[:-1] + if prefs['SHOWBR']: + # We don't want to flow quoted passages + if quoted==-1: L=L+'<br>' + else: + # If we're not adding <br> to each line, we'll need to + # insert <p> markup on blank lines to separate paragraphs. + if blankpat.match(L)!=-1: L=L+'<p>' + L=L+'\n' + output.write(L) + + if not prefs['SHOWHTML'] and not html_mode: output.write('</pre>') + if prefs['SHOWHR']: output.write('<hr>') + output.write('<!--next-->\n<!--endnext-->\n</body></html>') + output.close() + +def WriteHTMLIndex(prefs, fp, L, articles, indexname): + """Process a list L into an HTML index, written to fp. +L is processed from left to right, and contains a list of 2-tuples; +an integer of 1 or more giving the depth of indentation, and +a list of strings which are used to reference the 'articles' +dictionary. Most of the time the lists contain only 1 element.""" + fp.write('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 3.0//EN">\n' + "<html><head><base target=display>" + "<title>%s Mailing List Archive by %s</title></head><body>\n" + % (prefs['LABEL'], indexname)) + fp.write('<H1><A name="start">%s Mailing List Archive by %s</A></H1>' + '<ul><li> <b><a target="toc" href="#end">Most recent messages</a></b>' + '<li> <b>Messages sorted by:</b>' + % (prefs['LABEL'], indexname)) + if indexname!='Date': + fp.write('<a target="toc" href="date.html#start">[ date ]</a>') + if indexname!='Subject': + fp.write('<a target="toc" href="subject.html#start">[ subject ]</a>') + if indexname!='Author': + fp.write('<a target="toc" href="author.html#start">[ author ]</a>') + if indexname!='Thread': + fp.write('<a target="toc" href="thread.html#start">[ thread ]</a>') + if prefs['ARCHIVES']!='NONE': + fp.write('<li> <b><a href="%s">Other mail archives</a></b>' % + (prefs['ARCHIVES'],)) +# This doesn't look professional. -- JV +# mailcmd=prefs['MAILCOMMAND'] % {'TO':'amk@magnet.com'} +# fp.write('</ul><p>Please inform <A HREF="%s">amk@magnet.com</a> if any of the messages are formatted incorrectly.' % (mailcmd,) ) + + fp.write("<p><b>Starting:</b> <i>%s</i><br>" + "<b>Ending:</b> <i>%s</i><br><b>Messages:</b> %i<p>" + % (prefs['firstDate'], prefs['endDate'], len(L)) ) + + # Write the index + level=1 + fp.write('<ul>\n') + for indent, keys in L: + if indent>level and indent<=prefs['THRDLEVELS']: + fp.write((indent-level)*'<ul>'+'\n') + if indent<level: fp.write((level-indent)*'</ul>'+'\n') + level=indent + for j in keys: + subj, email, poster, date, datestr, parent, id=articles[j] + # XXX Should we put a mailto URL in here? + fp.write('<li> <A HREF="%s"><b>%s</b></a> <i>%s</i>\n' % + (j, subj, poster) ) + for i in range(0, indent): fp.write('</ul>') + fp.write('<p>') + + # Write the footer + import time + now=time.asctime(time.localtime(time.time())) + +# JV -- Fixed a bug here. + if prefs['ARCHIVES'] <> 'NONE': + otherstr=('<li> <b><a href="%s">Other mail archives</a></b>' % + (prefs['ARCHIVES'],) ) + else: otherstr="" + fp.write('<a name="end"><b>Last message date:</b></a> <i>%s</i><br>' + '<b>Archived on:</b> <i>%s</i><p><ul>' + '<li> <b>Messages sorted by:</b>' + '<a target="toc" href="date.html#start">[ date ]</a>' + '<a target="toc" href="subject.html#start">[ subject ]</a>' + '<a target="toc" href="author.html#start">[ author ]</a>' + '<a target="toc" href="thread.html#start">[ thread ]</a>' + '%s</ul><p>' % (prefs['endDate'], now, otherstr)) + + fp.write('<p><hr><i>This archive was generated by ' +# JV Updated the URL. + '<A HREF="http://www.magnet.com/~amk/python/pipermail.html">' + 'Pipermail %s</A>.</i></body></html>' % (VERSION,)) + +# Set the hard-wired preferences first +# JV Changed the SHOWHTML pref default to 0 because 1 looks bad. +prefs={'CONFIGFILE':'~/.pmrc', 'MBOX':'mbox', + 'ARCHIVES': 'NONE', 'ABOUT':'NONE', 'REVERSE':0, + 'SHOWHEADERS':0, 'SHOWHTML':0, 'LABEL':"", + 'DIR':'archive', 'DIRMODE':0755, + 'FILEMODE':0644, 'OVERWRITE':0, 'VERBOSE':0, + 'THRDLEVELS':3, 'SHOWBR':0, 'IQUOTES':1, + 'SHOWHR':1, 'MAILCOMMAND':'mailto:%(TO)s', + 'INDEXFILE':'NONE' +} + +# Read the ~/.pmrc file +ReadCfgFile(prefs) +# Read environment variables +ReadEnvironment(prefs) + +# Parse command-line options +import getopt +options, params=getopt.getopt(sys.argv[1:], 'a:b:c:d:l:m:s:uipvxzh?') +for option, value in options: + if option=='-a': prefs['ARCHIVES']=value + if option=='-b': prefs['ABOUT']=value + if option=='-c': prefs['CONFIGFILE']=value + if option=='-d': prefs['DIR']=value +# if option=='-f': prefs.frames=1 + if option=='-i': prefs['MBOX']='-' + if option=='-l': prefs['LABEL']=value + if option=='-m': prefs['MBOX']=value + if option=='-s': prefs['INDEXFILE']=value + if option=='-p' or option=='-v': prefs['VERBOSE']=1 + if option=='-x': prefs['OVERWRITE']=1 + if option=='-z' or option=='-h' or option=='-?': PrintUsage() + +# Set up various variables +articles={} ; sequence=0 +for key in ['INDEXFILE', 'MBOX', 'CONFIGFILE', 'DIR']: + prefs[key]=posixpath.expanduser(prefs[key]) + +if prefs['INDEXFILE']=='NONE': + if prefs['MBOX']!='-': + prefs['INDEXFILE']=prefs['MBOX']+'.pipermail' + else: prefs['INDEXFILE']='mbox.pipermail' + +# Read an index file, if one can be found +if not prefs['OVERWRITE']: + # Look for a file contained pickled state + import pickle + try: + if prefs['VERBOSE']: + print 'Attempting to read index file', prefs['INDEXFILE'] + f=open(prefs['INDEXFILE'], 'r') + articles, sequence =pickle.load(f) + f.close() + except IOError: + if prefs['VERBOSE']: print 'No index file found.' + pass # Ignore errors + +# Open the input file +if prefs['MBOX']=='-': prefs['MBOX']=sys.stdin +else: + if prefs['VERBOSE']: print 'Opening input file', prefs['MBOX'] + prefs['MBOX']=open(prefs['MBOX'], 'r') + +# Create the destination directory; if it already exists, we don't care +try: + os.mkdir(prefs['DIR'], prefs['DIRMODE']) + if prefs['VERBOSE']: print 'Directory %s created'%(prefs['DIR'],) +except os.error, (errno, errmsg): pass + +# Create various data structures: +# msgids maps Message-IDs to filenames. +# roots maps Subject lines to (date, filename) tuples, and is used to +# identify the oldest article with a given subject line for threading. + +msgids={} ; roots={} +for i in articles.keys(): + subject, email, poster, date, datestr, parent, id =articles[i] + if id: msgids[id]=i + if not roots.has_key(subject) or roots[subject]<date: + roots[subject]=(date, i) + +# Start processing the index +import mailbox +mbox=mailbox.UnixMailbox(prefs['MBOX']) +while (1): + m=mbox.next() + if not m: break + + filename='%04i.html' % (sequence,) + if prefs['VERBOSE']: sys.stdout.write("Processing "+filename+"\n") + # The apparently redundant str() actually catches the case where + # m.getheader() returns None. + subj=str(m.getheader('Subject')) + # Remove any number of 'Re:' prefixes from the subject line + while (1): + i=REpat.match(subj) + if i!=-1: subj=subj[i:] + else: break + # Locate an e-mail address + L=m.getheader('From') + # JV: sometimes there is no From header, so use the one from unixfrom. + if not L: + try: + L = string.split(m.unixfrom)[1] + except: + L = "***Unknown***" + email=None + i=emailpat.search(L) + if i!=-1: + length=emailpat.match(L[i:]) + email=L[i:i+length] + # Remove e-mail addresses inside angle brackets + poster=str(regsub.gsub('<.*>', '', L)) + # Check if there's a name in parentheses + i=paren_name_pat.match(poster) + if i!=-1: poster=paren_name_pat.group(1)[1:-1] + datestr=m.getheader('Date') + # JV -- Hacks to make the getdate work. + # These hacks might skew the post time a bit. + # Crude, but so far, effective. + words = string.split(datestr) + if ((len(words[-1]) == 4) and (len(words) == 5) + and (words[-1][:-1] == '199')): + try: + date = time.mktime(rfc822.parsedate('%s, %s %s %s %s' % + (words[0], words[2], words[1], + words[4], words[3]))) + except: + date = time.mktime(m.getdate('Date')) # Odd + elif len(words) > 4 and words[4][-1] == ',': + try: + date = time.mktime(rfc822.parsedate('%s, %s %s %s %s' % + (words[0], words[1], words[2], + words[3], words[4][:-1]))) + except: + date = time.mktime(m.getdate('Date')) # Hmm + else: + try: + date=time.mktime(m.getdate('Date')) + except: + print 'Error getting date!' + print 'Subject = ', m.getheader('subject') + print 'Date = ', m.getheader('date') + + id=m.getheader('Message-Id') + if id: id=id[1:-1] ; msgids[id]=filename + parent=None + in_reply_to=m.getheader('In-Reply-To') + if in_reply_to: + in_reply_to=in_reply_to[1:-1] + if msgids.has_key(in_reply_to): parent=msgids[in_reply_to] + elif roots.has_key(subj) and roots[subj][0]<date: + parent=roots[subj][1] + else: roots[subj]=(date,filename) + + articles[filename]=(subj, email, poster, date, datestr, parent, id) + ProcessMsgBody(prefs, m, filename, articles) + sequence=sequence+1 +prefs['MBOX'].close() + +if prefs['VERBOSE']: sys.stdout.write("Writing date index\n") +import time +indexname=posixpath.join(prefs['DIR'], 'date.html') +f=open(indexname, 'w') ; os.chmod(indexname, prefs['FILEMODE']) +dates=ListDict() +for i in articles.keys(): + subject, email, poster, date, datestr, parent, id=articles[i] + dates[date]=i +L=dates.keys() ; L.sort() +if prefs['REVERSE']: L.reverse() +prefs['firstDate']=time.asctime(time.localtime(L[0])) +prefs['endDate']=time.asctime(time.localtime(L[-1])) +L=map(lambda key, s=dates: (1,s[key]), L) +WriteHTMLIndex(prefs, f, L, articles, 'Date') +f.close() ; del dates, L + +if prefs['VERBOSE']: sys.stdout.write("Writing thread index\n") +indexname=posixpath.join(prefs['DIR'], 'thread.html') +f=open(indexname, 'w') ; os.chmod(indexname, prefs['FILEMODE']) +def DFS(p, N=None, depth=0, prefs=prefs): + set=filter(lambda x, N=N, p=p: p[x][1]==N, p.keys()) + set=map(lambda x, a=articles: (articles[x][3],x), set) + set.sort() + if prefs['REVERSE']: set.reverse() + set=map(lambda x: x[1], set) + if len(set)==0: return [(depth, [N])] + else: + L=[] + for i in set: + L=L+DFS(p, i, depth+1) + return [(depth,[N])]+L +parents={} +for i in articles.keys(): + subject, email, poster, date, datestr, parent, id=articles[i] + parents[i]=(date, parent) +L=DFS(parents)[1:] +WriteHTMLIndex(prefs, f, L, articles, 'Thread') +f.close() ; del L, parents + +if prefs['VERBOSE']: sys.stdout.write("Writing subject index\n") +indexname=posixpath.join(prefs['DIR'], 'subject.html') +f=open(indexname, 'w') ; os.chmod(indexname, prefs['FILEMODE']) +subjects=ListDict() +for i in articles.keys(): + subject, email, poster, date, datestr, parent, id=articles[i] + subjects[(subject, date)]=i +L=subjects.keys() ; L.sort() ; L=map(lambda key, s=subjects: (1, s[key]), L) +WriteHTMLIndex(prefs, f, L, articles, 'Subject') +f.close() ; del subjects, L + +if prefs['VERBOSE']: sys.stdout.write("Writing author index\n") +indexname=posixpath.join(prefs['DIR'], 'author.html') +f=open(indexname, 'w') ; os.chmod(indexname, prefs['FILEMODE']) +authors=ListDict() +for i in articles.keys(): + v=articles[i] + authors[(v[2],v[3])]=i +L=authors.keys() ; L.sort() ; L=map(lambda key, s=authors: (1,s[key]), L) +WriteHTMLIndex(prefs, f, L, articles, 'Author') +f.close() ; del authors, L + +if prefs['VERBOSE']: sys.stdout.write("Writing framed index\n") +f=open(posixpath.join(prefs['DIR'], 'blank.html'), 'w') +f.write("<html></html>") ; f.close() +# JV changed... +f=open(posixpath.join(prefs['DIR'], mm_cfg.HOME_PAGE), 'w') +f.write("""<html><head><title>%s Pipermail Archive</title> +<frameset cols="*, 60%%"> +<FRAME SRC="thread.html" NAME=toc> +<FRAME SRC="blank.html" NAME=display> +</frameset></head> +<body><noframes> +To access the %s Pipermail Archive, choose one of the following links: +<p> +Messages sorted by <a target="toc" href="date.html#start">[ date ] </a> +<a target="toc" href="subject.html#start">[ subject ]</a> +<a target="toc" href="author.html#start">[ author ]</a> +<a target="toc" href="thread.html#start">[ thread ]</a> +</ol> +</noframes> +</body></html> +""" % (prefs['LABEL'],prefs['LABEL']) ) + + +import pickle +if prefs['VERBOSE']: print 'Writing index file', prefs['INDEXFILE'] +f=open(prefs['INDEXFILE'], 'w') +pickle.dump( (articles, sequence), f ) +f.close() diff --git a/scripts/answer_majordomo_mail b/scripts/answer_majordomo_mail new file mode 100755 index 000000000..d063de234 --- /dev/null +++ b/scripts/answer_majordomo_mail @@ -0,0 +1,25 @@ +#!/usr/local/bin/python +# This is another script that's really just for my use, but +# feel free to use it if you know how... + +import sys + +f = open('/tmp/md.err', 'a+') +sys.stderr = f + +sys.path.append('/home/mailman/mailman/modules') + +import mm_message, mm_deliver + +msg = mm_message.IncomingMessage() + +text = open('/home/mailman/mailman/misc/majordomo.answer.txt', 'r').read() + +class MyDeliverer(mm_deliver.Deliverer): + def GetAdminEmail(self): + return "mailman-owner" + +d = MyDeliverer() + +d.SendTextToUser('Your mail to Majordomo...', text, msg.GetSender(), + 'mailman-owner') diff --git a/scripts/mailowner b/scripts/mailowner new file mode 100755 index 000000000..746dc8008 --- /dev/null +++ b/scripts/mailowner @@ -0,0 +1,26 @@ +#! /usr/local/bin/python +# +# This script gets called by the wrapper. +# Stdin is the mail message, and argv[1] is the name of the mailing list +# whose owner(s) to send mail to. + +import sys +f = open('/tmp/owner.errs', 'a+') +sys.stderr = f + +sys.path.append('/home/mailman/mailman/modules') + +import maillist, mm_message + +# Only let one program run at once per list. + +# TODO: This *can* fail, and should send back an error message when it does. +current_list = maillist.MailList(sys.argv[1]) +try: + msg = mm_message.IncomingMessage() + if not current_list.bounce_processing or not current_list.ScanMessage(msg): + current_list.DeliverToList(msg, current_list.owner, '', '') +# Let another process run. +finally: + current_list.Unlock() + diff --git a/scripts/owner b/scripts/owner new file mode 100755 index 000000000..746dc8008 --- /dev/null +++ b/scripts/owner @@ -0,0 +1,26 @@ +#! /usr/local/bin/python +# +# This script gets called by the wrapper. +# Stdin is the mail message, and argv[1] is the name of the mailing list +# whose owner(s) to send mail to. + +import sys +f = open('/tmp/owner.errs', 'a+') +sys.stderr = f + +sys.path.append('/home/mailman/mailman/modules') + +import maillist, mm_message + +# Only let one program run at once per list. + +# TODO: This *can* fail, and should send back an error message when it does. +current_list = maillist.MailList(sys.argv[1]) +try: + msg = mm_message.IncomingMessage() + if not current_list.bounce_processing or not current_list.ScanMessage(msg): + current_list.DeliverToList(msg, current_list.owner, '', '') +# Let another process run. +finally: + current_list.Unlock() + diff --git a/scripts/post b/scripts/post new file mode 100755 index 000000000..4eac017c6 --- /dev/null +++ b/scripts/post @@ -0,0 +1,69 @@ +#! /usr/local/bin/python +# +# This script gets called by the wrapper. +# Stdin is the mail message, and argv[1] is the name of the mailing list +# being posted to. +# Todo: check size of To: list < 100 +# Send back why the post was rejected. + +import sys +f = open('/tmp/hmm2', 'a+') +sys.stderr = f + +sys.path.append('/home/mailman/mailman/modules') + +import maillist, mm_message, mm_err, mm_cfg + +# Only let one program run at once per list. + +# TODO: This can fail, and should send back an error message when it does. +current_list = maillist.MailList(sys.argv[1]) + +try: + prog = current_list.filter_prog + if prog: + import os, __main__ + file = os.path.join('/home/mailman/mailman/filters', prog) + try: + execfile(file) + text = __main__.mailman_text + except: + text = sys.stdin.read() + else: + text = sys.stdin.read() + msg = mm_message.IncomingMessage(text) + + try: + current_list.Post(msg) + except mm_err.MMNeedApproval, err_msg: + if current_list.dont_respond_to_post_requests: + sys.exit(0) + the_sender = msg.GetSender() + subj = msg.getheader('subject') + if not subj: + subj = '[no subject]' + body = ''' +Your mail to '%s' with the subject: + +%s + +Is being held until the list moderator can review it for approval. +The reason why it is being held is: + + %s + +Either the message will get posted to the list, or you will receive +notification of the moderator's decison. +''' % (current_list.real_name, subj, err_msg) + + current_list.SendTextToUser( subject = 'Mail sent to %s' % + current_list.real_name, + recipient = the_sender, + text = body) +# Let another process run. +finally: + current_list.Unlock() + + + + diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 000000000..34adf817b --- /dev/null +++ b/src/Makefile @@ -0,0 +1,44 @@ +HOME=/home/mailman +MAILMAN=/home/mailman/mailman + +all: admin_wrapper admindb_wrapper archives_wrapper edithtml_wrapper options_wrapper listinfo_wrapper subscribe_wrapper handle_opts_wrapper mail_wrapper alias_wrapper + +admin_wrapper: + gcc -o ${HOME}/cgi-bin/admin admin-wrapper.c + chmod a+sx ${HOME}/cgi-bin/admin + +admindb_wrapper: + gcc -o ${HOME}/cgi-bin/admindb admindb-wrapper.c + chmod a+sx ${HOME}/cgi-bin/admindb + +archives_wrapper: + gcc -o ${HOME}/cgi-bin/archives archives-wrapper.c + chmod a+sx ${HOME}/cgi-bin/archives + +edithtml_wrapper: + gcc -o ${HOME}/cgi-bin/edithtml edithtml-wrapper.c + chmod a+sx ${HOME}/cgi-bin/edithtml + +options_wrapper: + gcc -o ${HOME}/cgi-bin/options options-wrapper.c + chmod a+sx ${HOME}/cgi-bin/options + +listinfo_wrapper: + gcc -o ${HOME}/cgi-bin/listinfo listinfo-wrapper.c + chmod a+sx ${HOME}/cgi-bin/listinfo + +subscribe_wrapper: + gcc -o ${HOME}/cgi-bin/subscribe subscribe-wrapper.c + chmod a+sx ${HOME}/cgi-bin/subscribe + +handle_opts_wrapper: + gcc -o ${HOME}/cgi-bin/handle_opts handle_opts-wrapper.c + chmod a+sx ${HOME}/cgi-bin/handle_opts + +mail_wrapper: + gcc -o ${MAILMAN}/mail/wrapper mail-wrapper.c + chmod a+sx ${MAILMAN}/mail/wrapper + +alias_wrapper: + gcc -o ${MAILMAN}/bin/addaliases alias-wrapper.c + chmod a+sx ${MAILMAN}/bin/addaliases diff --git a/src/admin-wrapper.c b/src/admin-wrapper.c new file mode 100644 index 000000000..df495da58 --- /dev/null +++ b/src/admin-wrapper.c @@ -0,0 +1,95 @@ +/* +** generic wrapper that will take info from a environment +** variable, and pass it to two commands. +** +** 10-17-96 : Hal Schechner +** 12-14-96 : John Viega -- changed to work on 1 command, +** take a list of valid commands, +** just pass on argv, and use execvp() +** Also threw in some useful feedback for when there's +** a failure, mainly for future debugging. +** +** Chmod this bitch 4755. +** +*/ +#include <stdio.h> + +const char *COMMAND = "/home/mailman/mailman/cgi/admin"; + +/* Might want to make this full path. + I can write whatever program named sendmail, + so this isn't much for security. +*/ +const char *LEGAL_PARENT_NAMES[] = { + "httpd", + NULL /* Sentinal, don't remove */ +}; + +/* Should make these arrays too... */ +const int LEGAL_PARENT_UID = 60001; /* nobody's UID */ +const int LEGAL_PARENT_GID = 60001; /* nobody's GID */ + + +/* +** what is the name of the process with pid of 'pid' +*/ +char *get_process_name(int pid) { + FILE *proc; + char fname[30]; + char tmp[255]; + static char procname[255]; + sprintf(fname, "/proc/%d/status", pid); + proc = fopen(fname, "r"); + fgets(tmp, 256, proc); + sscanf(tmp, "Name: %s\n", procname); + fclose(proc); + return procname; +} + + +int valid_parent(char *parent){ + int i = 0; + + while(LEGAL_PARENT_NAMES[i] != NULL) + { + if(!strcmp(parent, LEGAL_PARENT_NAMES[i])) + { + return 1; + } + i++; + } + return 0; +} + +/* +** is the parent process allowed to call us? +*/ +int legal_caller() { + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + printf("GOT UID %d.\n", getuid()); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + printf("GOT GID %d.\n", getgid()); + return 0; + } + return 1; +} + +void main(int argc, char **argv, char **env) { + char *command; + int i; + command = (char *)malloc(sizeof(char) * i); + + if(legal_caller()) { + setuid(geteuid()); + execve(COMMAND, &argv[0], env); + } + else { + printf("Illegal caller!\n"); + } +} + diff --git a/src/admindb-wrapper.c b/src/admindb-wrapper.c new file mode 100644 index 000000000..748d78812 --- /dev/null +++ b/src/admindb-wrapper.c @@ -0,0 +1,94 @@ +/* +** generic wrapper that will take info from a environment +** variable, and pass it to two commands. +** +** 10-17-96 : Hal Schechner +** 12-14-96 : John Viega -- changed to work on 1 command, +** take a list of valid commands, +** just pass on argv, and use execvp() +** Also threw in some useful feedback for when there's +** a failure, mainly for future debugging. +** +** Chmod this bitch 4755. +** +*/ +#include <stdio.h> + +const char *COMMAND = "/home/mailman/mailman/cgi/admindb"; + +/* Might want to make this full path. + I can write whatever program named sendmail, + so this isn't much for security. +*/ +const char *LEGAL_PARENT_NAMES[] = { + "httpd", + NULL /* Sentinal, don't remove */ +}; + +/* Should make these arrays too... */ +const int LEGAL_PARENT_UID = 60001; /* nobody's UID */ +const int LEGAL_PARENT_GID = 60001; /* nobody's GID */ + + +/* +** what is the name of the process with pid of 'pid' +*/ +char *get_process_name(int pid) { + FILE *proc; + char fname[30]; + char tmp[255]; + static char procname[255]; + sprintf(fname, "/proc/%d/status", pid); + proc = fopen(fname, "r"); + fgets(tmp, 256, proc); + sscanf(tmp, "Name: %s\n", procname); + fclose(proc); + return procname; +} + + +int valid_parent(char *parent){ + int i = 0; + + while(LEGAL_PARENT_NAMES[i] != NULL) + { + if(!strcmp(parent, LEGAL_PARENT_NAMES[i])) + { + return 1; + } + i++; + } + return 0; +} + +/* +** is the parent process allowed to call us? +*/ +int legal_caller() { + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + printf("GOT UID %d.\n", getuid()); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + printf("GOT GID %d.\n", getgid()); + return 0; + } + return 1; +} + +void main(int argc, char **argv, char **env) { + char *command; + int i; + command = (char *)malloc(sizeof(char) * i); + + if(legal_caller()) { + setuid(geteuid()); + execve(COMMAND, &argv[0], env); + } + else { + printf("Illegal caller!\n"); + } +} diff --git a/src/alias-wrapper.c b/src/alias-wrapper.c new file mode 100644 index 000000000..d335e0c1c --- /dev/null +++ b/src/alias-wrapper.c @@ -0,0 +1,83 @@ +#include <stdio.h> + + +const int LEGAL_PARENT_UID = 9001; /* mailman's UID */ +const int LEGAL_PARENT_GID = 6; /* mailman's GID */ + +const char* SENDMAIL_CMD = "/usr/sbin/sendmail"; +const char* ALIAS_FILE = "/etc/aliases"; +const char* WRAPPER = "/home/mailman/mailman/mail/wrapper"; + +/* +** is the parent process allowed to call us? +*/ +int LegalCaller() +{ + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + printf("GOT UID %d.\n", getuid()); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + printf("GOT GID %d.\n", getgid()); + return 0; + } + return 1; +} + +void AddListAliases(char *list) +{ + FILE *f; + int err = 0; + + f = fopen(ALIAS_FILE ,"a+"); + if (f == NULL) + { + err = 1; + f = stderr; + fprintf(f, "\n\n***********ERROR!!!!***********\n"); + fprintf(f, "Could not write to the /etc/aliases file.\n"); + fprintf(f, "Please become root, add the lines below to that file,\n"); + fprintf(f, "And then run the command %s -bi\n", SENDMAIL_CMD); + } + + fprintf(f, "\n\n#-- %s -- mailing list aliases:\n", list); + fprintf(f, "%s: \t|\"%s post %s\"\n", list, WRAPPER, list); + fprintf(f, "%s-admin: \t|\"%s mailowner %s\"\n", list, WRAPPER, list); + fprintf(f, "%s-request: \t|\"%s mailcmd %s\"\n", list, WRAPPER, list); + fprintf(f, "# I think we don't want this one... it'll change the unix from line...\n"); + fprintf(f, "#owner-%s: \t%s-admin\n", list, list); + fprintf(f, "#%s-owner: \t%s-admin\n", list, list); + fprintf(f, "\n"); + fclose(f); + + if (!err) + { + printf("Rebuilding alias database...\n"); + execlp(SENDMAIL_CMD, SENDMAIL_CMD, "-bi"); + } +} + +void main(int argc, char **argv, char **env) +{ + char *command; + int i; + + if(argc != 2) + { + printf("Usage: %s [list-name]\n", argv[0]); + exit(0); + } + if(LegalCaller()) + { + setuid(geteuid()); + AddListAliases(argv[1]); + } + else + { + printf("Illegal caller!\n"); + } +} + diff --git a/src/archives-wrapper.c b/src/archives-wrapper.c new file mode 100644 index 000000000..89a23934b --- /dev/null +++ b/src/archives-wrapper.c @@ -0,0 +1,95 @@ +/* +** generic wrapper that will take info from a environment +** variable, and pass it to two commands. +** +** 10-17-96 : Hal Schechner +** 12-14-96 : John Viega -- changed to work on 1 command, +** take a list of valid commands, +** just pass on argv, and use execvp() +** Also threw in some useful feedback for when there's +** a failure, mainly for future debugging. +** +** Chmod this bitch 4755. +** +*/ +#include <stdio.h> + +const char *COMMAND = "/home/mailman/mailman/cgi/archives"; + +/* Might want to make this full path. + I can write whatever program named sendmail, + so this isn't much for security. +*/ +const char *LEGAL_PARENT_NAMES[] = { + "httpd", + NULL /* Sentinal, don't remove */ +}; + +/* Should make these arrays too... */ +const int LEGAL_PARENT_UID = 60001; /* nobody's UID */ +const int LEGAL_PARENT_GID = 60001; /* nobody's GID */ + + +/* +** what is the name of the process with pid of 'pid' +*/ +char *get_process_name(int pid) { + FILE *proc; + char fname[30]; + char tmp[255]; + static char procname[255]; + sprintf(fname, "/proc/%d/status", pid); + proc = fopen(fname, "r"); + fgets(tmp, 256, proc); + sscanf(tmp, "Name: %s\n", procname); + fclose(proc); + return procname; +} + + +int valid_parent(char *parent){ + int i = 0; + + while(LEGAL_PARENT_NAMES[i] != NULL) + { + if(!strcmp(parent, LEGAL_PARENT_NAMES[i])) + { + return 1; + } + i++; + } + return 0; +} + +/* +** is the parent process allowed to call us? +*/ +int legal_caller() { + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + printf("GOT UID %d.\n", getuid()); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + printf("GOT GID %d.\n", getgid()); + return 0; + } + return 1; +} + +void main(int argc, char **argv, char **env) { + char *command; + int i; + command = (char *)malloc(sizeof(char) * i); + + if(legal_caller()) { + setuid(geteuid()); + execve(COMMAND, &argv[0], env); + } + else { + printf("Illegal caller!\n"); + } +} + diff --git a/src/edithtml-wrapper.c b/src/edithtml-wrapper.c new file mode 100644 index 000000000..08a08ecfa --- /dev/null +++ b/src/edithtml-wrapper.c @@ -0,0 +1,95 @@ +/* +** generic wrapper that will take info from a environment +** variable, and pass it to two commands. +** +** 10-17-96 : Hal Schechner +** 12-14-96 : John Viega -- changed to work on 1 command, +** take a list of valid commands, +** just pass on argv, and use execvp() +** Also threw in some useful feedback for when there's +** a failure, mainly for future debugging. +** +** Chmod this bitch 4755. +** +*/ +#include <stdio.h> + +const char *COMMAND = "/home/mailman/mailman/cgi/edithtml"; + +/* Might want to make this full path. + I can write whatever program named sendmail, + so this isn't much for security. +*/ +const char *LEGAL_PARENT_NAMES[] = { + "httpd", + NULL /* Sentinal, don't remove */ +}; + +/* Should make these arrays too... */ +const int LEGAL_PARENT_UID = 60001; /* nobody's UID */ +const int LEGAL_PARENT_GID = 60001; /* nobody's GID */ + + +/* +** what is the name of the process with pid of 'pid' +*/ +char *get_process_name(int pid) { + FILE *proc; + char fname[30]; + char tmp[255]; + static char procname[255]; + sprintf(fname, "/proc/%d/status", pid); + proc = fopen(fname, "r"); + fgets(tmp, 256, proc); + sscanf(tmp, "Name: %s\n", procname); + fclose(proc); + return procname; +} + + +int valid_parent(char *parent){ + int i = 0; + + while(LEGAL_PARENT_NAMES[i] != NULL) + { + if(!strcmp(parent, LEGAL_PARENT_NAMES[i])) + { + return 1; + } + i++; + } + return 0; +} + +/* +** is the parent process allowed to call us? +*/ +int legal_caller() { + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + printf("GOT UID %d.\n", getuid()); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + printf("GOT GID %d.\n", getgid()); + return 0; + } + return 1; +} + +void main(int argc, char **argv, char **env) { + char *command; + int i; + command = (char *)malloc(sizeof(char) * i); + + if(legal_caller()) { + setuid(geteuid()); + execve(COMMAND, &argv[0], env); + } + else { + printf("Illegal caller!\n"); + } +} + diff --git a/src/handle_opts-wrapper.c b/src/handle_opts-wrapper.c new file mode 100644 index 000000000..bb3800a36 --- /dev/null +++ b/src/handle_opts-wrapper.c @@ -0,0 +1,99 @@ +/* +** generic wrapper that will take info from a environment +** variable, and pass it to two commands. +** +** 10-17-96 : Hal Schechner +** 12-14-96 : John Viega -- changed to work on 1 command, +** take a list of valid commands, +** just pass on argv, and use execvp() +** Also threw in some useful feedback for when there's +** a failure, mainly for future debugging. +** +** Chmod this bitch 4755. +** +*/ +#include <stdio.h> + +const char *COMMAND = "/home/mailman/mailman/cgi/handle_opts"; + +FILE *f; + +/* Might want to make this full path. + I can write whatever program named sendmail, + so this isn't much for security. +*/ +const char *LEGAL_PARENT_NAMES[] = { + "httpd", + NULL /* Sentinal, don't remove */ +}; + +/* Should make these arrays too... */ +const int LEGAL_PARENT_UID = 60001; /* nobody's UID */ +const int LEGAL_PARENT_GID = 60001; /* nobody's GID */ + + +/* +** what is the name of the process with pid of 'pid' +*/ +char *get_process_name(int pid) { + FILE *proc; + char fname[30]; + char tmp[255]; + static char procname[255]; + sprintf(fname, "/proc/%d/status", pid); + proc = fopen(fname, "r"); + fgets(tmp, 256, proc); + sscanf(tmp, "Name: %s\n", procname); + fclose(proc); + return procname; +} + + +int valid_parent(char *parent){ + int i = 0; + + while(LEGAL_PARENT_NAMES[i] != NULL) + { + if(!strcmp(parent, LEGAL_PARENT_NAMES[i])) + { + return 1; + } + i++; + } + return 0; +} + +/* +** is the parent process allowed to call us? +*/ +int legal_caller() { + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + fprintf(f,"GOT UID %d.\n", getuid()); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + fprintf(f,"GOT GID %d.\n", getgid()); + return 0; + } + return 1; +} + +void main(int argc, char **argv, char **env) { + char *command; + int i; + + f = fopen("/tmp/zozo", "w+"); + command = (char *)malloc(sizeof(char) * i); + + if(legal_caller()) { + setuid(geteuid()); + execve(COMMAND, &argv[0], env); + } + else { + fprintf(f,"Illegal caller!\n"); + } +} + diff --git a/src/listinfo-wrapper.c b/src/listinfo-wrapper.c new file mode 100644 index 000000000..fd42fe131 --- /dev/null +++ b/src/listinfo-wrapper.c @@ -0,0 +1,96 @@ +/* +** generic wrapper that will take info from a environment +** variable, and pass it to two commands. +** +** 10-17-96 : Hal Schechner +** 12-14-96 : John Viega -- changed to work on 1 command, +** take a list of valid commands, +** just pass on argv, and use execvp() +** Also threw in some useful feedback for when there's +** a failure, mainly for future debugging. +** +** Chmod this bitch 4755. +** +*/ +#include <stdio.h> + +const char *COMMAND = "/home/mailman/mailman/cgi/listinfo"; + + +/* Might want to make this full path. + I can write whatever program named sendmail, + so this isn't much for security. +*/ +const char *LEGAL_PARENT_NAMES[] = { + "httpd", + NULL /* Sentinal, don't remove */ +}; + +/* Should make these arrays too... */ +const int LEGAL_PARENT_UID = 60001; /* nobody's UID */ +const int LEGAL_PARENT_GID = 60001; /* nobody's GID */ + + +/* +** what is the name of the process with pid of 'pid' +*/ +char *get_process_name(int pid) { + FILE *proc; + char fname[30]; + char tmp[255]; + static char procname[255]; + sprintf(fname, "/proc/%d/status", pid); + proc = fopen(fname, "r"); + fgets(tmp, 256, proc); + sscanf(tmp, "Name: %s\n", procname); + fclose(proc); + return procname; +} + + +int valid_parent(char *parent){ + int i = 0; + + while(LEGAL_PARENT_NAMES[i] != NULL) + { + if(!strcmp(parent, LEGAL_PARENT_NAMES[i])) + { + return 1; + } + i++; + } + return 0; +} + +/* +** is the parent process allowed to call us? +*/ +int legal_caller() { + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + printf("GOT UID %d.\n", getuid()); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + printf("GOT GID %d.\n", getgid()); + return 0; + } + return 1; +} + +void main(int argc, char **argv, char **env) { + char *command; + int i; + command = (char *)malloc(sizeof(char) * i); + + if(legal_caller()) { + argv[0] = (char *)COMMAND; + execve(COMMAND, argv, env); + } + else { + printf("Illegal caller!\n"); + } +} + diff --git a/src/options-wrapper.c b/src/options-wrapper.c new file mode 100644 index 000000000..3a87dfd4c --- /dev/null +++ b/src/options-wrapper.c @@ -0,0 +1,95 @@ +/* +** generic wrapper that will take info from a environment +** variable, and pass it to two commands. +** +** 10-17-96 : Hal Schechner +** 12-14-96 : John Viega -- changed to work on 1 command, +** take a list of valid commands, +** just pass on argv, and use execvp() +** Also threw in some useful feedback for when there's +** a failure, mainly for future debugging. +** +** Chmod this bitch 4755. +** +*/ +#include <stdio.h> + +const char *COMMAND = "/home/mailman/mailman/cgi/options"; + + +/* Might want to make this full path. + I can write whatever program named sendmail, + so this isn't much for security. +*/ +const char *LEGAL_PARENT_NAMES[] = { + "httpd", + NULL /* Sentinal, don't remove */ +}; + +/* Should make these arrays too... */ +const int LEGAL_PARENT_UID = 60001; /* nobody's UID */ +const int LEGAL_PARENT_GID = 60001; /* nobody's GID */ + + +/* +** what is the name of the process with pid of 'pid' +*/ +char *get_process_name(int pid) { + FILE *proc; + char fname[30]; + char tmp[255]; + static char procname[255]; + sprintf(fname, "/proc/%d/status", pid); + proc = fopen(fname, "r"); + fgets(tmp, 256, proc); + sscanf(tmp, "Name: %s\n", procname); + fclose(proc); + return procname; +} + + +int valid_parent(char *parent){ + int i = 0; + + while(LEGAL_PARENT_NAMES[i] != NULL) + { + if(!strcmp(parent, LEGAL_PARENT_NAMES[i])) + { + return 1; + } + i++; + } + return 0; +} + +/* +** is the parent process allowed to call us? +*/ +int legal_caller() { + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + printf("GOT UID %d.\n", getuid()); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + printf("GOT GID %d.\n", getgid()); + return 0; + } + return 1; +} + +void main(int argc, char **argv, char **env) { + char *command; + int i; + command = (char *)malloc(sizeof(char) * i); + + if(legal_caller()) { + execve(COMMAND, &argv[0], env); + } + else { + printf("Illegal caller!\n"); + } +} + diff --git a/src/subscribe-wrapper.c b/src/subscribe-wrapper.c new file mode 100644 index 000000000..bd1fc1582 --- /dev/null +++ b/src/subscribe-wrapper.c @@ -0,0 +1,106 @@ +/* +** generic wrapper that will take info from a environment +** variable, and pass it to two commands. +** +** 10-17-96 : Hal Schechner +** 12-14-96 : John Viega -- changed to work on 1 command, +** take a list of valid commands, +** just pass on argv, and use execvp() +** Also threw in some useful feedback for when there's +** a failure, mainly for future debugging. +** +** Chmod this bitch 4755. +** +*/ +#include <stdio.h> + +const char *COMMAND = "/home/mailman/mailman/cgi/subscribe"; +FILE *f; + +/* Might want to make this full path. + I can write whatever program named sendmail, + so this isn't much for security. +*/ +const char *LEGAL_PARENT_NAMES[] = { + "httpd", + NULL /* Sentinal, don't remove */ +}; + +/* Should make these arrays too... */ +const int LEGAL_PARENT_UID = 60001; /* nobody's UID */ +const int LEGAL_PARENT_GID = 60001; /* nobody's GID */ + + +/* +** what is the name of the process with pid of 'pid' +*/ +char *get_process_name(int pid) { + FILE *proc; + char fname[30]; + char tmp[255]; + static char procname[255]; + sprintf(fname, "/proc/%d/status", pid); + proc = fopen(fname, "r"); + fgets(tmp, 256, proc); + sscanf(tmp, "Name: %s\n", procname); + fclose(proc); + return procname; +} + + +int valid_parent(char *parent){ + int i = 0; + + while(LEGAL_PARENT_NAMES[i] != NULL) + { + if(!strcmp(parent, LEGAL_PARENT_NAMES[i])) + { + return 1; + } + i++; + } + return 0; +} + +/* +** is the parent process allowed to call us? +*/ +int legal_caller() { + /* compare to our parent's uid */ + if(LEGAL_PARENT_UID != getuid()) + { + fprintf(f,"GOT UID %d.\n", getuid()); + fflush(f); + return 0; + } + if(LEGAL_PARENT_GID != getgid()) + { + fprintf(f,"GOT GID %d.\n", getgid()); + fflush(f); + return 0; + } + return 1; +} + +void main(int argc, char **argv, char **env) { + char *command; + int i; + command = (char *)malloc(sizeof(char) * i); + + f = fopen("/tmp/wtf_man","w+"); + fprintf(f, "Hello...\n"); + fflush(f); + if(legal_caller()) { + setuid(geteuid()); + fprintf(f, "Sheesh...\n"); + fflush(f); + execve(COMMAND, &argv[0], env); + fprintf(f, "Damn, I suck.\n"); + fflush(f); + } + else { + fprintf(f,"Illegal caller!\n"); + fflush(f); + } +} + diff --git a/templates/archives.html b/templates/archives.html new file mode 100644 index 000000000..efcd4a0f2 --- /dev/null +++ b/templates/archives.html @@ -0,0 +1,17 @@ +<html> +<body> +<title> <MM-List-Name> Archives </title> + +<h1> <MM-List-Name> Archives:</h1> + +<font size = +2> +<!--- This tag isn't very flexible. Feel free to look at the html it + generates, and hard code this page. The volume number only goes + up once a year, on the new year. +---> +<MM-Archive-List> +</font> + +<MM-Mailman-Footer> +</body> +</html> diff --git a/templates/handle_opts.html b/templates/handle_opts.html new file mode 100644 index 000000000..aee76b3ec --- /dev/null +++ b/templates/handle_opts.html @@ -0,0 +1,7 @@ +<html> +<body> +<h1><MM-List-Name> <MM-Operation> Results</h1> +<MM-Results> +<MM-Mailman-Footer> +</body> +</html>
\ No newline at end of file diff --git a/templates/options.html b/templates/options.html new file mode 100644 index 000000000..3ee259b20 --- /dev/null +++ b/templates/options.html @@ -0,0 +1,73 @@ +<html> +<body> +<h1><MM-List-Name> Configuration for <MM-User></h1> +<hr> +<ul> + <li> <a href=#reminder>Receive Password Reminder</a> + <li> <a href=#options>Change Options (turn digest on/off, etc...)</a> + <li> <a href=#unsub>Unsubscribe</a> + <li> <a href=#changepw>Change your password</a> +</ul> +<hr> +<MM-Form-Start> +<a name=reminder> +<h3> Send Password reminder</h3> +If you've forgotten your password, click this button, and it will be +emailed to the address you're subscribed from.<p> +<MM-Email-My-Pw><p> + +<a name=options> +<h3> User Options </h3> +<strong>Current values are checked.</strong><p> +<table border=1><tr><td> +<strong> Set Digest Mode</strong> <br> +If you turn digest mode on, you'll get posts bundled together instead of one post at a time.<br> +<MM-Digest-Radio-Button> On +<MM-Undigest-Radio-Button> Off<p> +</td></tr> +<tr><td> +<strong> Disable mail delivery </strong> <br> +Turn this on if you want mail not to be delivered to you for a little while.<br> +<mm-delivery-enable-button> Off +<mm-delivery-disable-button> On <p> +</td></tr> +<tr><td> +<strong> Get Fancy Digests or Text Digests?</strong> <br> +If you have any problems with digests, select plain text. <br> +<MM-Plain-Digests-Button> Plain Text +<MM-Fancy-Digests-Button> Fancy (MIME) <p> +</td></tr> +<tr><td> +<strong> Receive posts you send to the list? </strong><br> +<mm-receive-own-mail-button> Yes +<mm-dont-receive-own-mail-button> No <p> +</td></tr> +<tr><td> +<strong> Receive acknowlegement mail when you send mail to the list? </strong><br> +<mm-ack-posts-button> Yes +<mm-dont-ack-posts-button> No <p> +</td></tr> +<tr><td> +<strong> Conceal yourself from subscriber list? </strong><br> +<MM-Hide-Subscription-Button> Yes +<MM-Public-Subscription-Button> No <p> +</td></tr> +<tr><td> +Password: <MM-Digest-Pw-Box> <MM-Digest-Submit><p> +</td></tr></table> +<a name=unsub> +<h3>Unsubscribe</h3> +<MM-Unsubscribe-Button> +<font size=+1>Your Privacy password:</font> <MM-Unsub-Pw-Box><p> +<hr> +<a name=changepw> +<h3>Change Your Password</h3> +<font size=+1>Old password: </font> <MM-Old-Pw-Box><p> +<font size=+1>New password: </font> <MM-New-Pass-Box><p> +<font size=+1>Again to confirm:</font> <MM-Confirm-Pass-Box><p> +<MM-Change-Pass-Button><p> +<MM-Form-End> + +<MM-Mailman-Footer> +</body> +</html> diff --git a/templates/subscribe.html b/templates/subscribe.html new file mode 100644 index 000000000..96535faa3 --- /dev/null +++ b/templates/subscribe.html @@ -0,0 +1,7 @@ +<html> +<body> +<h1><MM-List-Name> Subscription results</h1> +<MM-Results> +<MM-Mailman-Footer> +</body> +</html>
\ No newline at end of file |
