summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--DONE97
-rw-r--r--Mailman/Errors.py16
-rw-r--r--Mailman/Mailbox.py16
-rw-r--r--Mailman/Message.py150
-rw-r--r--Mailman/SecurityManager.py59
-rw-r--r--Mailman/aliases.py41
-rw-r--r--Mailman/pipermail.py552
-rw-r--r--README86
-rw-r--r--TODO83
-rwxr-xr-xbin/addaliasesbin0 -> 8148 bytes
-rwxr-xr-xbin/digest_arch130
-rwxr-xr-xbin/populate_new_list62
-rwxr-xr-xbin/subscribe_enmasse13
-rwxr-xr-xfilters/bowa-strip17
-rw-r--r--modules/aliases.py41
-rw-r--r--modules/mm_err.py16
-rw-r--r--modules/mm_mbox.py16
-rw-r--r--modules/mm_message.py150
-rw-r--r--modules/mm_security.py59
-rw-r--r--modules/nohup.out0
-rw-r--r--modules/pipermail.py552
-rwxr-xr-xscripts/answer_majordomo_mail25
-rwxr-xr-xscripts/mailowner26
-rwxr-xr-xscripts/owner26
-rwxr-xr-xscripts/post69
-rw-r--r--src/Makefile44
-rw-r--r--src/admin-wrapper.c95
-rw-r--r--src/admindb-wrapper.c94
-rw-r--r--src/alias-wrapper.c83
-rw-r--r--src/archives-wrapper.c95
-rw-r--r--src/edithtml-wrapper.c95
-rw-r--r--src/handle_opts-wrapper.c99
-rw-r--r--src/listinfo-wrapper.c96
-rw-r--r--src/options-wrapper.c95
-rw-r--r--src/subscribe-wrapper.c106
-rw-r--r--templates/archives.html17
-rw-r--r--templates/handle_opts.html7
-rw-r--r--templates/options.html73
-rw-r--r--templates/subscribe.html7
39 files changed, 3308 insertions, 0 deletions
diff --git a/DONE b/DONE
new file mode 100644
index 000000000..f99a0304a
--- /dev/null
+++ b/DONE
@@ -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()
diff --git a/README b/README
new file mode 100644
index 000000000..4cbc96f81
--- /dev/null
+++ b/README
@@ -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...
+
+
+
diff --git a/TODO b/TODO
new file mode 100644
index 000000000..4c44dec56
--- /dev/null
+++ b/TODO
@@ -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
new file mode 100755
index 000000000..537f077d9
--- /dev/null
+++ b/bin/addaliases
Binary files differ
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