summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Mailman/Utils.py241
1 files changed, 91 insertions, 150 deletions
diff --git a/Mailman/Utils.py b/Mailman/Utils.py
index 9594bf7f5..a14ff8947 100644
--- a/Mailman/Utils.py
+++ b/Mailman/Utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc.
+# Copyright (C) 1998,1999,2000,2001 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -27,20 +27,21 @@ import sys
import os
import string
import re
-from UserDict import UserDict
-from types import StringType
# XXX: obsolete, should use re module
import regsub
import random
import urlparse
+import sha
+import errno
+
+from mimelib.MsgReader import MsgReader
from Mailman import mm_cfg
from Mailman import Errors
-##try:
-## import md5
-##except ImportError:
-## md5 = None
-from Mailman import Crypt
+from Mailman.SafeDict import SafeDict
+
+EMPTYSTRING = ''
+NL = '\n'
@@ -384,7 +385,7 @@ def MakeRandomPassword(length=6):
syls = []
while len(syls)*2 < length:
syls.append(random.choice(_syllables))
- return string.join(syls, '')[:length]
+ return EMPTYSTRING.join(syls)[:length]
def GetRandomSeed():
chr1 = int(random.random() * 52)
@@ -398,19 +399,24 @@ def GetRandomSeed():
return "%c%c" % tuple(map(mkletter, (chr1, chr2)))
def SetSiteAdminPassword(pw):
- fp = open_ex(mm_cfg.SITE_PW_FILE, 'w', perms=0640)
- fp.write(Crypt.crypt(pw, GetRandomSeed()))
- fp.close()
+ omask = os.umask(026) # rw-r-----
+ try:
+ fp = open(mm_cfg.SITE_PW_FILE, 'w')
+ fp.write(sha.new(pw).hexdigest() + '\n')
+ fp.close()
+ finally:
+ os.umask(omask)
-def CheckSiteAdminPassword(pw1):
+def CheckSiteAdminPassword(response):
try:
- f = open(mm_cfg.SITE_PW_FILE)
- pw2 = f.read()
- f.close()
- return Crypt.crypt(pw1, pw2[:2]) == pw2
- # There probably is no site admin password if there was an exception
- except IOError:
+ fp = open(mm_cfg.SITE_PW_FILE)
+ challenge = fp.read()[-1] # strip off trailing nl
+ fp.close()
+ except IOError, e:
+ if e.errno <> errno.ENOENT: raise
+ # It's okay not to have a site admin password, just return false
return 0
+ return challenge == sha.new(response).hexdigest()
@@ -469,28 +475,6 @@ def chunkify(members, chunksize=None):
-class SafeDict(UserDict):
- """Dictionary which returns a default value for unknown keys.
-
- This is used in maketext so that editing templates is a bit more robust.
- """
- def __init__(self, d=None):
- # optional initial dictionary is a Python 1.5.2-ism. Do it this way
- # for portability
- UserDict.__init__(self)
- if d is not None:
- self.update(d)
-
- def __getitem__(self, key):
- try:
- return self.data[key]
- except KeyError:
- if type(key) == StringType:
- return '%('+key+')s'
- else:
- return '<Missing key: %s>' % `key`
-
-
def maketext(templatefile, dict=None, raw=0, lang=None):
"""Make some text from a template file.
@@ -513,52 +497,61 @@ def maketext(templatefile, dict=None, raw=0, lang=None):
-# given a Message.Message object, test for administrivia (eg subscribe,
-# unsubscribe, etc). the test must be a good guess -- messages that return
+ADMINDATA = {
+ # admin keyword: (minimum #args, maximum #args)
+ 'subscribe': (0, 3),
+ 'unsubscribe': (0, 1),
+ 'who': (0, 0),
+ 'info': (0, 0),
+ 'lists': (0, 0),
+ 'set': (3, 3),
+ 'help': (0, 0),
+ 'password': (2, 2),
+ 'options': (0, 0),
+ 'remove': (0, 0),
+ }
+
+# Given a Message.Message object, test for administrivia (eg subscribe,
+# unsubscribe, etc). The test must be a good guess -- messages that return
# true get sent to the list admin instead of the entire list.
-#
-def IsAdministrivia(msg):
- lines = map(string.lower, string.split(msg.body, "\n"))
- # check to see how many lines that actually have text in them there are
- admin_data = {"subscribe": (0, 3),
- "unsubscribe": (0, 1),
- "who": (0,0),
- "info": (0,0),
- "lists": (0,0),
- "set": (3, 3),
- "help": (0,0),
- "password": (2, 2),
- "options": (0,0),
- "remove": (0, 0)}
- lines_with_text = 0
- for line in lines:
- if string.strip(line):
- lines_with_text = lines_with_text + 1
- if lines_with_text > mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES:
+def is_administrivia(msg):
+ reader = MsgReader(msg)
+ linecnt = 0
+ lines = []
+ while 1:
+ line = reader.readline()
+ if not line:
+ break
+ # Strip out any signatures
+ if line == '-- ':
+ break
+ if line.strip():
+ linecnt += 1
+ if linecnt > mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES:
return 0
- sig_ind = string.find(msg.body, "\n-- ")
- if sig_ind != -1:
- body = msg.body[:sig_ind]
- else:
- body = msg.body
- if admin_data.has_key(string.lower(string.strip(body))):
+ lines.append(line)
+ bodytext = NL.join(lines)
+ # See if the body text has only one word, and that word is administrivia
+ if ADMINDATA.has_key(bodytext.strip().lower()):
return 1
- try:
- if admin_data.has_key(string.lower(string.strip(msg["subject"]))):
- return 1
- except KeyError:
- pass
+ # See if the subect has only one word, and that word is administrivia
+ if ADMINDATA.has_key(msg.get('subject', '').strip().lower()):
+ return 1
+ # Look at the first N lines and see if there is any administrivia on the
+ # line. BAW: N is currently hardcoded to 5.
for line in lines[:5]:
- if not string.strip(line):
+ if not line.strip():
continue
- words = string.split(line)
- if admin_data.has_key(words[0]):
- min_args, max_args = admin_data[words[0]]
- if min_args <= len(words[1:]) <= max_args:
- if (words[0] == 'set'
- and (words[2] not in ['on', 'off'])):
- continue
- return 1
+ words = [word.lower() for word in line.split()]
+ minargs, maxargs = ADMINDATA.get(words[0], (None, None))
+ if minargs is None and maxargs is None:
+ continue
+ if minargs <= len(words[1:]) <= maxargs:
+ # Special case the `set' keyword. BAW: I don't know why this is
+ # here.
+ if words[0] == 'set' and words[2] not in ('on', 'off'):
+ continue
+ return 1
return 0
@@ -577,46 +570,6 @@ Two differences from os.mkdir():
-def open_ex(filename, mode='r', bufsize=-1, perms=0664):
- """Use os.open() to open a file in a particular mode.
-
- Returns a file-like object instead of a file descriptor.
- Also umask is forced to 0 during the open().
-
- `b' flag is currently unsupported."""
- modekey = mode
- trunc = os.O_TRUNC
- if mode == 'r':
- trunc = 0
- elif mode[-1] == '+':
- trunc = 0
- modekey = mode[:-1]
- else:
- trunc = os.O_TRUNC
- flags = {'r' : os.O_RDONLY,
- 'w' : os.O_WRONLY | os.O_CREAT,
- 'a' : os.O_RDWR | os.O_CREAT | os.O_APPEND,
- 'rw': os.O_RDWR | os.O_CREAT,
- # TBD: should also support `b'
- }.get(modekey)
- if flags is None:
- raise TypeError, 'Unsupported file mode: ' + mode
- flags = flags | trunc
- ou = os.umask(0)
- try:
- try:
- fd = os.open(filename, flags, perms)
- fp = os.fdopen(fd, mode, bufsize)
- return fp
- # transform any os.errors into IOErrors
- except OSError, e:
- e.__class__ = IOError
- raise IOError, e, sys.exc_info()[2]
- finally:
- os.umask(ou)
-
-
-
def GetRequestURI(fallback=None):
"""Return the full virtual path this CGI script was invoked with.
@@ -639,7 +592,7 @@ def GetRequestURI(fallback=None):
# Wait on a dictionary of child pids
-def reap(kids, func=None):
+def reap(kids, func=None, once=0):
while kids:
if func:
func()
@@ -650,33 +603,21 @@ def reap(kids, func=None):
except KeyError:
# Huh? How can this happen?
pass
+ if once:
+ break
-# Useful conversion routines
-# unhexlify(hexlify(s)) == s
-#
-# Python 2.0 has these in the binascii module
-try:
- from binascii import hexlify, unhexlify
-except ImportError:
- # Not the most efficient of implementations, but good enough for older
- # versions of Python.
- def hexlify(s):
- acc = []
- def munge(byte, append=acc.append, a=ord('a'), z=ord('0')):
- if byte > 9: append(byte+a-10)
- else: append(byte+z)
- for c in s:
- hi, lo = divmod(ord(c), 16)
- munge(hi)
- munge(lo)
- return string.join(map(chr, acc), '')
+def GetDirectories(path):
+ from os import listdir
+ from os.path import isdir, join
+ return [f for f in listdir(path) if isdir(join(path, f))]
+
+
+def GetLanguageDescr(lang):
+ return mm_cfg.LC_DESCRIPTIONS[lang][0]
+
- def unhexlify(s):
- acc = []
- append = acc.append
- # In Python 2.0, we can use the int() built-in
- int16 = string.atoi
- for i in range(0, len(s), 2):
- append(chr(int16(s[i:i+2], 16)))
- return string.join(acc, '')
+def GetCharSet():
+ # BAW: Hmm...
+ lang = os.environ['LANG']
+ return mm_cfg.LC_DESCRIPTIONS[lang][1]