From 6ce18f4dde15fedba6210a201f5662d6daff3e2a Mon Sep 17 00:00:00 2001 From: Alain Reguera Delgado Date: Aug 28 2012 20:16:25 +0000 Subject: Update `trunk/Identity/Webenv/' directory structure: - Move Puntal, Punbb and Mailman directories from `App' to 'Themes/Default' directory. --- diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/Archiver.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/Archiver.py deleted file mode 100644 index ba611c6..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/Archiver.py +++ /dev/null @@ -1,251 +0,0 @@ -# Copyright (C) 1998-2003 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -"""Mixin class for putting new messages in the right place for archival. - -Public archives are separated from private ones. An external archival -mechanism (eg, pipermail) should be pointed to the right places, to do the -archival. -""" - -import os -import errno -import traceback -import re -from cStringIO import StringIO - -from Mailman import mm_cfg -from Mailman import Mailbox -from Mailman import Utils -from Mailman import Site -from Mailman.SafeDict import SafeDict -from Mailman.Logging.Syslog import syslog -from Mailman.i18n import _ -from Mailman.htmlformat import Link -from Mailman.htmlformat import HTMLFormatObject - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -def makelink(old, new): - try: - os.symlink(old, new) - except OSError, e: - if e.errno <> errno.EEXIST: - raise - -def breaklink(link): - try: - os.unlink(link) - except OSError, e: - if e.errno <> errno.ENOENT: - raise - - - -class Archiver: - # - # Interface to Pipermail. HyperArch.py uses this method to get the - # archive directory for the mailing list - # - def InitVars(self): - # Configurable - self.archive = mm_cfg.DEFAULT_ARCHIVE - # 0=public, 1=private: - self.archive_private = mm_cfg.DEFAULT_ARCHIVE_PRIVATE - self.archive_volume_frequency = \ - mm_cfg.DEFAULT_ARCHIVE_VOLUME_FREQUENCY - # The archive file structure by default is: - # - # archives/ - # private/ - # listname.mbox/ - # listname.mbox - # listname/ - # lots-of-pipermail-stuff - # public/ - # listname.mbox@ -> ../private/listname.mbox - # listname@ -> ../private/listname - # - # IOW, the mbox and pipermail archives are always stored in the - # private archive for the list. This is safe because archives/private - # is always set to o-rx. Public archives have a symlink to get around - # the private directory, pointing directly to the private/listname - # which has o+rx permissions. Private archives do not have the - # symbolic links. - omask = os.umask(0) - try: - try: - os.mkdir(self.archive_dir()+'.mbox', 02775) - except OSError, e: - if e.errno <> errno.EEXIST: raise - # We also create an empty pipermail archive directory into - # which we'll drop an empty index.html file into. This is so - # that lists that have not yet received a posting have - # /something/ as their index.html, and don't just get a 404. - try: - os.mkdir(self.archive_dir(), 02775) - except OSError, e: - if e.errno <> errno.EEXIST: raise - # See if there's an index.html file there already and if not, - # write in the empty archive notice. - indexfile = os.path.join(self.archive_dir(), 'index.html') - fp = None - try: - fp = open(indexfile) - except IOError, e: - if e.errno <> errno.ENOENT: raise - omask = os.umask(002) - try: - fp = open(indexfile, 'w') - finally: - os.umask(omask) - fp.write(Utils.maketext( - 'emptyarchive.html', - {'listname': self.real_name, - 'listname_lower': self.real_name.lower(), - 'listinfo': self.GetScriptURL('listinfo', absolute=1), - # Links on header section (errormsg) - 'listadmin_link': Link(Utils.ScriptURL('admin'), _('Administration')).Format(), - 'listinfo_link': Link(Utils.ScriptURL('listinfo'), _('General Information')).Format(), - 'errormsg_header': _('Mailing Lists'), - }, mlist=self)) - if fp: - fp.close() - finally: - os.umask(omask) - - def archive_dir(self): - return Site.get_archpath(self.internal_name()) - - def ArchiveFileName(self): - """The mbox name where messages are left for archive construction.""" - return os.path.join(self.archive_dir() + '.mbox', - self.internal_name() + '.mbox') - - def GetBaseArchiveURL(self): - url = self.GetScriptURL('private', absolute=1) + '/' - if self.archive_private: - return url - else: - hostname = re.match('[^:]*://([^/]*)/.*', url).group(1)\ - or mm_cfg.DEFAULT_URL_HOST - url = mm_cfg.PUBLIC_ARCHIVE_URL % { - 'listname': self.internal_name(), - 'hostname': hostname - } - if not url.endswith('/'): - url += '/' - return url - - def __archive_file(self, afn): - """Open (creating, if necessary) the named archive file.""" - omask = os.umask(002) - try: - return Mailbox.Mailbox(open(afn, 'a+')) - finally: - os.umask(omask) - - # - # old ArchiveMail function, retained under a new name - # for optional archiving to an mbox - # - def __archive_to_mbox(self, post): - """Retain a text copy of the message in an mbox file.""" - try: - afn = self.ArchiveFileName() - mbox = self.__archive_file(afn) - mbox.AppendMessage(post) - mbox.fp.close() - except IOError, msg: - syslog('error', 'Archive file access failure:\n\t%s %s', afn, msg) - raise - - def ExternalArchive(self, ar, txt): - d = SafeDict({'listname': self.internal_name(), - 'hostname': self.host_name, - }) - cmd = ar % d - extarch = os.popen(cmd, 'w') - extarch.write(txt) - status = extarch.close() - if status: - syslog('error', 'external archiver non-zero exit status: %d\n', - (status & 0xff00) >> 8) - - # - # archiving in real time this is called from list.post(msg) - # - def ArchiveMail(self, msg): - """Store postings in mbox and/or pipermail archive, depending.""" - # Fork so archival errors won't disrupt normal list delivery - if mm_cfg.ARCHIVE_TO_MBOX == -1: - return - # - # We don't need an extra archiver lock here because we know the list - # itself must be locked. - if mm_cfg.ARCHIVE_TO_MBOX in (1, 2): - self.__archive_to_mbox(msg) - if mm_cfg.ARCHIVE_TO_MBOX == 1: - # Archive to mbox only. - return - txt = str(msg) - # should we use the internal or external archiver? - private_p = self.archive_private - if mm_cfg.PUBLIC_EXTERNAL_ARCHIVER and not private_p: - self.ExternalArchive(mm_cfg.PUBLIC_EXTERNAL_ARCHIVER, txt) - elif mm_cfg.PRIVATE_EXTERNAL_ARCHIVER and private_p: - self.ExternalArchive(mm_cfg.PRIVATE_EXTERNAL_ARCHIVER, txt) - else: - # use the internal archiver - f = StringIO(txt) - import HyperArch - h = HyperArch.HyperArchive(self) - h.processUnixMailbox(f) - h.close() - f.close() - - # - # called from MailList.MailList.Save() - # - def CheckHTMLArchiveDir(self): - # We need to make sure that the archive directory has the right perms - # for public vs private. If it doesn't exist, or some weird - # permissions errors prevent us from stating the directory, it's - # pointless to try to fix the perms, so we just return -scott - if mm_cfg.ARCHIVE_TO_MBOX == -1: - # Archiving is completely disabled, don't require the skeleton. - return - pubdir = Site.get_archpath(self.internal_name(), public=True) - privdir = self.archive_dir() - pubmbox = pubdir + '.mbox' - privmbox = privdir + '.mbox' - if self.archive_private: - breaklink(pubdir) - breaklink(pubmbox) - else: - # BAW: privdir or privmbox could be nonexistant. We'd get an - # OSError, ENOENT which should be caught and reported properly. - makelink(privdir, pubdir) - # Only make this link if the site has enabled public mbox files - if mm_cfg.PUBLIC_MBOX: - makelink(privmbox, pubmbox) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/HyperArch.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/HyperArch.py deleted file mode 100644 index c319629..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/HyperArch.py +++ /dev/null @@ -1,1344 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""HyperArch: Pipermail archiving for Mailman - - - The Dragon De Monsyne - - TODO: - - Should be able to force all HTML to be regenerated next time the - archive is run, in case a template is changed. - - Run a command to generate tarball of html archives for downloading - (probably in the 'update_dirty_archives' method). -""" - -from __future__ import nested_scopes - -import sys -import re -import errno -import urllib -import time -import os -import types -import HyperDatabase -import pipermail -import weakref -import binascii - -from email.Header import decode_header, make_header -from email.Errors import HeaderParseError -from email.Charset import Charset - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import Errors -from Mailman import LockFile -from Mailman import MailList -from Mailman import i18n -from Mailman.SafeDict import SafeDict -from Mailman.Logging.Syslog import syslog -from Mailman.Mailbox import ArchiverMailbox -from Mailman.htmlformat import Link -from Mailman.htmlformat import HTMLFormatObject - -# Set up i18n. Assume the current language has already been set in the caller. -_ = i18n._ - -gzip = None -if mm_cfg.GZIP_ARCHIVE_TXT_FILES: - try: - import gzip - except ImportError: - pass - -EMPTYSTRING = '' -NL = '\n' - -# MacOSX has a default stack size that is too small for deeply recursive -# regular expressions. We see this as crashes in the Python test suite when -# running test_re.py and test_sre.py. The fix is to set the stack limit to -# 2048; the general recommendation is to do in the shell before running the -# test suite. But that's inconvenient for a daemon like the qrunner. -# -# AFAIK, this problem only affects the archiver, so we're adding this work -# around to this file (it'll get imported by the bundled pipermail or by the -# bin/arch script. We also only do this on darwin, a.k.a. MacOSX. -if sys.platform == 'darwin': - try: - import resource - except ImportError: - pass - else: - soft, hard = resource.getrlimit(resource.RLIMIT_STACK) - newsoft = min(hard, max(soft, 1024*2048)) - resource.setrlimit(resource.RLIMIT_STACK, (newsoft, hard)) - - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -def html_quote(s, lang=None): - repls = ( ('&', '&'), - ("<", '<'), - (">", '>'), - ('"', '"')) - for thing, repl in repls: - s = s.replace(thing, repl) - return Utils.uncanonstr(s, lang) - - -def url_quote(s): - return urllib.quote(s) - -def null_to_space(s): - return s.replace('\000', ' ') - - -def sizeof(filename, lang): - try: - size = os.path.getsize(filename) - except OSError, e: - # ENOENT can happen if the .mbox file was moved away or deleted, and - # an explicit mbox file name was given to bin/arch. - if e.errno <> errno.ENOENT: raise - return _('size not available') - if size < 1000: - # Avoid i18n side-effects - otrans = i18n.get_translation() - try: - i18n.set_language(lang) - out = _(' %(size)i bytes ') - finally: - i18n.set_translation(otrans) - return out - elif size < 1000000: - return ' %d KB ' % (size / 1000) - # GB?? :-) - return ' %d MB ' % (size / 1000000) - - -html_charset = '' - -def CGIescape(arg, lang=None): - if isinstance(arg, types.UnicodeType): - s = Utils.websafe(arg) - else: - s = Utils.websafe(str(arg)) - return Utils.uncanonstr(s.replace('"', '"'), lang) - -# Parenthesized human name -paren_name_pat = re.compile(r'([(].*[)])') - -# Subject lines preceded with 'Re:' -REpat = re.compile( r"\s*RE\s*(\[\d+\]\s*)?:\s*", re.IGNORECASE) - -# E-mail addresses and URLs in text -emailpat = re.compile(r'([-+,.\w]+@[-+.\w]+)') - -# Argh! This pattern is buggy, and will choke on URLs with GET parameters. -urlpat = re.compile(r'(\w+://[^>)\s]+)') # URLs in text - -# Blank lines -blankpat = re.compile(r'^\s*$') - -# Starting directive -htmlpat = re.compile(r'^\s*\s*$', re.IGNORECASE) -# Ending directive -nohtmlpat = re.compile(r'^\s*\s*$', re.IGNORECASE) -# Match quoted text -quotedpat = re.compile(r'^([>|:]|>)+') - - - -# Like Utils.maketext() but with caching to improve performance. -# -# _templatefilepathcache is used to associate a (templatefile, lang, listname) -# key with the file system path to a template file. This path is the one that -# the Utils.findtext() function has computed is the one to match the values in -# the key tuple. -# -# _templatecache associate a file system path as key with the text -# returned after processing the contents of that file by Utils.findtext() -# -# We keep two caches to reduce the amount of template text kept in memory, -# since the _templatefilepathcache is a many->one mapping and _templatecache -# is a one->one mapping. Imagine 1000 lists all using the same default -# English template. - -_templatefilepathcache = {} -_templatecache = {} - -def quick_maketext(templatefile, dict=None, lang=None, mlist=None): - if mlist is None: - listname = '' - else: - listname = mlist._internal_name - if lang is None: - if mlist is None: - lang = mm_cfg.DEFAULT_SERVER_LANGUAGE - else: - lang = mlist.preferred_language - cachekey = (templatefile, lang, listname) - filepath = _templatefilepathcache.get(cachekey) - if filepath: - template = _templatecache.get(filepath) - if filepath is None or template is None: - # Use the basic maketext, with defaults to get the raw template - template, filepath = Utils.findtext(templatefile, lang=lang, - raw=True, mlist=mlist) - _templatefilepathcache[cachekey] = filepath - _templatecache[filepath] = template - # Copied from Utils.maketext() - text = template - if dict is not None: - try: - sdict = SafeDict(dict) - try: - text = sdict.interpolate(template) - except UnicodeError: - # Try again after coercing the template to unicode - utemplate = unicode(template, - Utils.GetCharSet(lang), - 'replace') - text = sdict.interpolate(utemplate) - except (TypeError, ValueError): - # The template is really screwed up - pass - # Make sure the text is in the given character set, or html-ify any bogus - # characters. - return Utils.uncanonstr(text, lang) - - - -# Note: I'm overriding most, if not all of the pipermail Article class -# here -ddm -# The Article class encapsulates a single posting. The attributes are: -# -# sequence : Sequence number, unique for each article in a set of archives -# subject : Subject -# datestr : The posting date, in human-readable format -# date : The posting date, in purely numeric format -# fromdate : The posting date, in `unixfrom' format -# headers : Any other headers of interest -# author : The author's name (and possibly organization) -# email : The author's e-mail address -# msgid : A unique message ID -# in_reply_to : If !="", this is the msgid of the article being replied to -# references: A (possibly empty) list of msgid's of earlier articles in -# the thread -# body : A list of strings making up the message body - -class Article(pipermail.Article): - __super_init = pipermail.Article.__init__ - __super_set_date = pipermail.Article._set_date - - _last_article_time = time.time() - - def __init__(self, message=None, sequence=0, keepHeaders=[], - lang=mm_cfg.DEFAULT_SERVER_LANGUAGE, mlist=None): - self.__super_init(message, sequence, keepHeaders) - self.prev = None - self.next = None - # Trim Re: from the subject line - i = 0 - while i != -1: - result = REpat.match(self.subject) - if result: - i = result.end(0) - self.subject = self.subject[i:] - else: - i = -1 - # Useful to keep around - self._lang = lang - self._mlist = mlist - - if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: - # Avoid i18n side-effects. Note that the language for this - # article (for this list) could be different from the site-wide - # preferred language, so we need to ensure no side-effects will - # occur. Think what happens when executing bin/arch. - otrans = i18n.get_translation() - try: - i18n.set_language(lang) - if self.author == self.email: - self.author = self.email = re.sub('@', _(' at '), - self.email) - else: - self.email = re.sub('@', _(' at '), self.email) - finally: - i18n.set_translation(otrans) - - # Snag the content-* headers. RFC 1521 states that their values are - # case insensitive. - ctype = message.get('Content-Type', 'text/plain') - cenc = message.get('Content-Transfer-Encoding', '') - self.ctype = ctype.lower() - self.cenc = cenc.lower() - self.decoded = {} - cset = Utils.GetCharSet(mlist.preferred_language) - cset_out = Charset(cset).output_charset or cset - charset = message.get_content_charset(cset_out) - if charset: - charset = charset.lower().strip() - if charset[0]=='"' and charset[-1]=='"': - charset = charset[1:-1] - if charset[0]=="'" and charset[-1]=="'": - charset = charset[1:-1] - try: - body = message.get_payload(decode=True) - except binascii.Error: - body = None - if body and charset != Utils.GetCharSet(self._lang): - # decode body - try: - body = unicode(body, charset) - except (UnicodeError, LookupError): - body = None - if body: - self.body = [l + "\n" for l in body.splitlines()] - - self.decode_headers() - - # Mapping of listnames to MailList instances as a weak value dictionary. - # This code is copied from Runner.py but there's one important operational - # difference. In Runner.py, we always .Load() the MailList object for - # each _dispose() run, otherwise the object retrieved from the cache won't - # be up-to-date. Since we're creating a new HyperArchive instance for - # each message being archived, we don't need to worry about that -- but it - # does mean there are additional opportunities for optimization. - _listcache = weakref.WeakValueDictionary() - - def _open_list(self, listname): - # Cache the open list so that any use of the list within this process - # uses the same object. We use a WeakValueDictionary so that when the - # list is no longer necessary, its memory is freed. - mlist = self._listcache.get(listname) - if not mlist: - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - syslog('error', 'error opening list: %s\n%s', listname, e) - return None - else: - self._listcache[listname] = mlist - return mlist - - def __getstate__(self): - d = self.__dict__.copy() - # We definitely don't want to pickle the MailList instance, so just - # pickle a reference to it. - if d.has_key('_mlist'): - mlist = d['_mlist'] - del d['_mlist'] - else: - mlist = None - if mlist: - d['__listname'] = self._mlist.internal_name() - else: - d['__listname'] = None - # Delete a few other things we don't want in the pickle - for attr in ('prev', 'next', 'body'): - if d.has_key(attr): - del d[attr] - d['body'] = [] - return d - - def __setstate__(self, d): - # For loading older Articles via pickle. All this stuff was added - # when Simone Piunni and Tokio Kikuchi i18n'ified Pipermail. See SF - # patch #594771. - self.__dict__ = d - listname = d.get('__listname') - if listname: - del d['__listname'] - d['_mlist'] = self._open_list(listname) - if not d.has_key('_lang'): - if hasattr(self, '_mlist'): - self._lang = self._mlist.preferred_language - else: - self._lang = mm_cfg.DEFAULT_SERVER_LANGUAGE - if not d.has_key('cenc'): - self.cenc = None - if not d.has_key('decoded'): - self.decoded = {} - - def setListIfUnset(self, mlist): - if getattr(self, '_mlist', None) is None: - self._mlist = mlist - - def quote(self, buf): - return html_quote(buf, self._lang) - - def decode_headers(self): - """MIME-decode headers. - - If the email, subject, or author attributes contain non-ASCII - characters using the encoded-word syntax of RFC 2047, decoded versions - of those attributes are placed in the self.decoded (a dictionary). - - If the list's charset differs from the header charset, an attempt is - made to decode the headers as Unicode. If that fails, they are left - undecoded. - """ - author = self.decode_charset(self.author) - subject = self.decode_charset(self.subject) - if author: - self.decoded['author'] = author - email = self.decode_charset(self.email) - if email: - self.decoded['email'] = email - if subject: - if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: - otrans = i18n.get_translation() - try: - i18n.set_language(self._lang) - atmark = unicode(_(' at '), Utils.GetCharSet(self._lang)) - subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)', - '\g<1>' + atmark + '\g<2>', subject) - finally: - i18n.set_translation(otrans) - self.decoded['subject'] = subject - self.decoded['stripped'] = self.strip_subject(subject or self.subject) - - def strip_subject(self, subject): - # Strip subject_prefix and Re: for subject sorting - # This part was taken from CookHeaders.py (TK) - prefix = self._mlist.subject_prefix.strip() - if prefix: - prefix_pat = re.escape(prefix) - prefix_pat = '%'.join(prefix_pat.split(r'\%')) - prefix_pat = re.sub(r'%\d*d', r'\s*\d+\s*', prefix_pat) - subject = re.sub(prefix_pat, '', subject) - subject = subject.lstrip() - strip_pat = re.compile('^((RE|AW|SV|VS)(\[\d+\])?:\s*)+', re.I) - stripped = strip_pat.sub('', subject) - return stripped - - def decode_charset(self, field): - # TK: This function was rewritten for unifying to Unicode. - # Convert 'field' into Unicode one line string. - try: - pairs = decode_header(field) - ustr = make_header(pairs).__unicode__() - except (LookupError, UnicodeError, ValueError, HeaderParseError): - # assume list's language - cset = Utils.GetCharSet(self._mlist.preferred_language) - if cset == 'us-ascii': - cset = 'iso-8859-1' # assume this for English list - ustr = unicode(field, cset, 'replace') - return u''.join(ustr.splitlines()) - - def as_html(self): - d = self.__dict__.copy() - # avoid i18n side-effects - otrans = i18n.get_translation() - i18n.set_language(self._lang) - try: - d["prev"], d["prev_wsubj"] = self._get_prev() - d["next"], d["next_wsubj"] = self._get_next() - - d["email_html"] = self.quote(self.email) - d["title"] = self.quote(self.subject) - d["subject_html"] = self.quote(self.subject) - d["subject_url"] = url_quote(self.subject) - d["in_reply_to_url"] = url_quote(self._message_id) - if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: - # Point the mailto url back to the list - author = re.sub('@', _(' at '), self.author) - emailurl = self._mlist.GetListEmail() - else: - author = self.author - emailurl = self.email - d["author_html"] = self.quote(author) - d["email_url"] = url_quote(emailurl) - d["datestr_html"] = self.quote(i18n.ctime(int(self.date))) - d["body"] = self._get_body() - d["listurl"] = self._mlist.GetScriptURL('listinfo', absolute=1) - d["listname"] = self._mlist.real_name - d["encoding"] = '' - - # Links on header section (errormsg) - d["listadmin_link"] = Link(Utils.ScriptURL('admin'), _('Administration')).Format() - d["listinfo_link"] = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() - d["errormsg_header"] = _('Mailing Lists') - - finally: - i18n.set_translation(otrans) - - charset = Utils.GetCharSet(self._lang) - d["encoding"] = html_charset % charset - - self._add_decoded(d) - return quick_maketext( - 'article.html', d, - lang=self._lang, mlist=self._mlist) - - def _get_prev(self): - """Return the href and subject for the previous message""" - if self.prev: - subject = self._get_subject_enc(self.prev) - prev = ('' - % (url_quote(self.prev.filename))) - prev_wsubj = ('
  • ' + _('Previous message:') + - ' %s\n
  • ' - % (url_quote(self.prev.filename), - self.quote(subject))) - else: - prev = prev_wsubj = "" - return prev, prev_wsubj - - def _get_subject_enc(self, art): - """Return the subject of art, decoded if possible. - - If the charset of the current message and art match and the - article's subject is encoded, decode it. - """ - return art.decoded.get('subject', art.subject) - - def _get_next(self): - """Return the href and subject for the previous message""" - if self.next: - subject = self._get_subject_enc(self.next) - next = ('' - % (url_quote(self.next.filename))) - next_wsubj = ('
  • ' + _('Next message:') + - ' %s\n
  • ' - % (url_quote(self.next.filename), - self.quote(subject))) - else: - next = next_wsubj = "" - return next, next_wsubj - - _rx_quote = re.compile('=([A-F0-9][A-F0-9])') - _rx_softline = re.compile('=[ \t]*$') - - def _get_body(self): - """Return the message body ready for HTML, decoded if necessary""" - try: - body = self.html_body - except AttributeError: - body = self.body - return null_to_space(EMPTYSTRING.join(body)) - - def _add_decoded(self, d): - """Add encoded-word keys to HTML output""" - for src, dst in (('author', 'author_html'), - ('email', 'email_html'), - ('subject', 'subject_html'), - ('subject', 'title')): - if self.decoded.has_key(src): - d[dst] = self.quote(self.decoded[src]) - - def as_text(self): - d = self.__dict__.copy() - # We need to guarantee a valid From_ line, even if there are - # bososities in the headers. - if not d.get('fromdate', '').strip(): - d['fromdate'] = time.ctime(time.time()) - if not d.get('email', '').strip(): - d['email'] = 'bogus@does.not.exist.com' - if not d.get('datestr', '').strip(): - d['datestr'] = time.ctime(time.time()) - # - headers = ['From %(email)s %(fromdate)s', - 'From: %(email)s (%(author)s)', - 'Date: %(datestr)s', - 'Subject: %(subject)s'] - if d['_in_reply_to']: - headers.append('In-Reply-To: %(_in_reply_to)s') - if d['_references']: - headers.append('References: %(_references)s') - if d['_message_id']: - headers.append('Message-ID: %(_message_id)s') - body = EMPTYSTRING.join(self.body) - cset = Utils.GetCharSet(self._lang) - # Coerce the body to Unicode and replace any invalid characters. - if not isinstance(body, types.UnicodeType): - body = unicode(body, cset, 'replace') - if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: - otrans = i18n.get_translation() - try: - atmark = unicode(_(' at '), cset) - i18n.set_language(self._lang) - body = re.sub(r'([-+,.\w]+)@([-+.\w]+)', - '\g<1>' + atmark + '\g<2>', body) - finally: - i18n.set_translation(otrans) - # Return body to character set of article. - body = body.encode(cset, 'replace') - return NL.join(headers) % d + '\n\n' + body + '\n' - - def _set_date(self, message): - self.__super_set_date(message) - self.fromdate = time.ctime(int(self.date)) - - def loadbody_fromHTML(self,fileobj): - self.body = [] - begin = 0 - while 1: - line = fileobj.readline() - if not line: - break - if not begin: - if line.strip() == '': - begin = 1 - continue - if line.strip() == '': - break - self.body.append(line) - - def finished_update_article(self): - self.body = [] - try: - del self.html_body - except AttributeError: - pass - - -class HyperArchive(pipermail.T): - __super_init = pipermail.T.__init__ - __super_update_archive = pipermail.T.update_archive - __super_update_dirty_archives = pipermail.T.update_dirty_archives - __super_add_article = pipermail.T.add_article - - # some defaults - DIRMODE = 02775 - FILEMODE = 0660 - - VERBOSE = 0 - DEFAULTINDEX = 'thread' - ARCHIVE_PERIOD = 'month' - - THREADLAZY = 0 - THREADLEVELS = 3 - - ALLOWHTML = 1 # "Lines between " handled as is. - SHOWHTML = 0 # Eg, nuke leading whitespace in html manner. - IQUOTES = 1 # Italicize quoted text. - SHOWBR = 0 # Add
    onto every line - - def __init__(self, maillist): - # can't init the database while other processes are writing to it! - # XXX TODO- implement native locking - # with mailman's LockFile module for HyperDatabase.HyperDatabase - # - dir = maillist.archive_dir() - db = HyperDatabase.HyperDatabase(dir, maillist) - self.__super_init(dir, reload=1, database=db) - - self.maillist = maillist - self._lock_file = None - self.lang = maillist.preferred_language - self.charset = Utils.GetCharSet(maillist.preferred_language) - - if hasattr(self.maillist,'archive_volume_frequency'): - if self.maillist.archive_volume_frequency == 0: - self.ARCHIVE_PERIOD='year' - elif self.maillist.archive_volume_frequency == 2: - self.ARCHIVE_PERIOD='quarter' - elif self.maillist.archive_volume_frequency == 3: - self.ARCHIVE_PERIOD='week' - elif self.maillist.archive_volume_frequency == 4: - self.ARCHIVE_PERIOD='day' - else: - self.ARCHIVE_PERIOD='month' - - yre = r'(?P[0-9]{4,4})' - mre = r'(?P[01][0-9])' - dre = r'(?P[0123][0-9])' - self._volre = { - 'year': '^' + yre + '$', - 'quarter': '^' + yre + r'q(?P[1234])$', - 'month': '^' + yre + r'-(?P[a-zA-Z]+)$', - 'week': r'^Week-of-Mon-' + yre + mre + dre, - 'day': '^' + yre + mre + dre + '$' - } - - def _makeArticle(self, msg, sequence): - return Article(msg, sequence, - lang=self.maillist.preferred_language, - mlist=self.maillist) - - def html_foot(self): - # avoid i18n side-effects - mlist = self.maillist - otrans = i18n.get_translation() - i18n.set_language(mlist.preferred_language) - # Convenience - def quotetime(s): - return html_quote(i18n.ctime(s), self.lang) - try: - d = {"lastdate": quotetime(self.lastdate), - "archivedate": quotetime(self.archivedate), - "listinfo": mlist.GetScriptURL('listinfo', absolute=1), - "version": self.version, - } - i = {"thread": _("thread"), - "subject": _("subject"), - "author": _("author"), - "date": _("date") - } - finally: - i18n.set_translation(otrans) - - for t in i.keys(): - cap = t[0].upper() + t[1:] - if self.type == cap: - d["%s_ref" % (t)] = "" - else: - d["%s_ref" % (t)] = ('[ %s ]' - % (t, i[t])) - return quick_maketext( - 'archidxfoot.html', d, - mlist=mlist) - - def html_head(self): - # avoid i18n side-effects - mlist = self.maillist - otrans = i18n.get_translation() - i18n.set_language(mlist.preferred_language) - # Convenience - def quotetime(s): - return html_quote(i18n.ctime(s), self.lang) - - try: - d = {"listname": html_quote(mlist.real_name, self.lang), - "archtype": self.type, - "archive": self.volNameToDesc(self.archive), - "listinfo": mlist.GetScriptURL('listinfo', absolute=1), - "firstdate": quotetime(self.firstdate), - "lastdate": quotetime(self.lastdate), - "size": self.size, - } - i = {"thread": _("thread"), - "subject": _("subject"), - "author": _("author"), - "date": _("date"), - } - finally: - i18n.set_translation(otrans) - - for t in i.keys(): - cap = t[0].upper() + t[1:] - if self.type == cap: - d["%s_ref" % (t)] = "" - d["archtype"] = i[t] - else: - d["%s_ref" % (t)] = ('[ %s ]' - % (t, i[t])) - if self.charset: - d["encoding"] = html_charset % self.charset - else: - d["encoding"] = "" - - # Links on header section (errormsg) - d["listadmin_link"] = Link(Utils.ScriptURL('admin'), _('Administration')).Format() - d["listinfo_link"] = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() - d["errormsg_header"] = _('Mailing Lists') - - return quick_maketext( - 'archidxhead.html', d, - mlist=mlist) - - def html_TOC(self): - mlist = self.maillist - listname = mlist.internal_name() - mbox = os.path.join(mlist.archive_dir()+'.mbox', listname+'.mbox') - d = {"listname": mlist.real_name, - "listinfo": mlist.GetScriptURL('listinfo', absolute=1), - "fullarch": '../%s.mbox/%s.mbox' % (listname, listname), - "size": sizeof(mbox, mlist.preferred_language), - 'meta': '', - } - # Avoid i18n side-effects - otrans = i18n.get_translation() - i18n.set_language(mlist.preferred_language) - try: - if not self.archives: - d["noarchive_msg"] = _( - 'Currently, there are no archives.') - d["archive_listing_start"] = "" - d["archive_listing_end"] = "" - d["archive_listing"] = "" - else: - d["noarchive_msg"] = "" - d["archive_listing_start"] = quick_maketext( - 'archliststart.html', - lang=mlist.preferred_language, - mlist=mlist) - d["archive_listing_end"] = quick_maketext( - 'archlistend.html', - mlist=mlist) - - accum = [] - highlight = 1 - for a in self.archives: - # Highlight TOC's rows - if highlight == 1: - css = 'class="title"' - highlight = 0 - else: - css = '' - highlight = 1 - accum.append(self.html_TOC_entry(a, css)) - d["archive_listing"] = EMPTYSTRING.join(accum) - finally: - i18n.set_translation(otrans) - # The TOC is always in the charset of the list's preferred language - d['meta'] += html_charset % Utils.GetCharSet(mlist.preferred_language) - # Links on header section (errormsg) - d['listadmin_link'] = Link(Utils.ScriptURL('admin'), _('Administration')).Format() - d['listinfo_link'] = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() - d['errormsg_header'] = _('Mailing Lists') - # The site can disable public access to the mbox file. - if mm_cfg.PUBLIC_MBOX: - template = 'archtoc.html' - else: - template = 'archtocnombox.html' - return quick_maketext(template, d, mlist=mlist) - - def html_TOC_entry(self, arch, css): - # Check to see if the archive is gzip'd or not - txtfile = os.path.join(self.maillist.archive_dir(), arch + '.txt') - gzfile = txtfile + '.gz' - # which exists? .txt.gz first, then .txt - if os.path.exists(gzfile): - file = gzfile - url = arch + '.txt.gz' - templ = '[ ' + _('Gzip\'d Text%(sz)s') \ - + ']' - elif os.path.exists(txtfile): - file = txtfile - url = arch + '.txt' - templ = '[ ' + _('Text%(sz)s') + ']' - else: - # neither found? - file = None - # in Python 1.5.2 we have an easy way to get the size - if file: - textlink = templ % { - 'url': url, - 'sz' : sizeof(file, self.maillist.preferred_language) - } - else: - # there's no archive file at all... hmmm. - textlink = '' - return quick_maketext( - 'archtocentry.html', - {'archive': arch, - 'archivelabel': self.volNameToDesc(arch), - 'css': css, - 'textlink': textlink - }, - mlist=self.maillist) - - def GetArchLock(self): - if self._lock_file: - return 1 - self._lock_file = LockFile.LockFile( - os.path.join(mm_cfg.LOCK_DIR, - self.maillist.internal_name() + '-arch.lock')) - try: - self._lock_file.lock(timeout=0.5) - except LockFile.TimeOutError: - return 0 - return 1 - - def DropArchLock(self): - if self._lock_file: - self._lock_file.unlock(unconditionally=1) - self._lock_file = None - - def processListArch(self): - name = self.maillist.ArchiveFileName() - wname= name+'.working' - ename= name+'.err_unarchived' - try: - os.stat(name) - except (IOError,os.error): - #no archive file, nothin to do -ddm - return - - #see if arch is locked here -ddm - if not self.GetArchLock(): - #another archiver is running, nothing to do. -ddm - return - - #if the working file is still here, the archiver may have - # crashed during archiving. Save it, log an error, and move on. - try: - wf = open(wname) - syslog('error', - 'Archive working file %s present. ' - 'Check %s for possibly unarchived msgs', - wname, ename) - omask = os.umask(007) - try: - ef = open(ename, 'a+') - finally: - os.umask(omask) - ef.seek(1,2) - if ef.read(1) <> '\n': - ef.write('\n') - ef.write(wf.read()) - ef.close() - wf.close() - os.unlink(wname) - except IOError: - pass - os.rename(name,wname) - archfile = open(wname) - self.processUnixMailbox(archfile) - archfile.close() - os.unlink(wname) - self.DropArchLock() - - def get_filename(self, article): - return '%06i.html' % (article.sequence,) - - def get_archives(self, article): - """Return a list of indexes where the article should be filed. - A string can be returned if the list only contains one entry, - and the empty list is legal.""" - res = self.dateToVolName(float(article.date)) - self.message(_("figuring article archives\n")) - self.message(res + "\n") - return res - - def volNameToDesc(self, volname): - volname = volname.strip() - # Don't make these module global constants since we have to runtime - # translate them anyway. - monthdict = [ - '', - _('January'), _('February'), _('March'), _('April'), - _('May'), _('June'), _('July'), _('August'), - _('September'), _('October'), _('November'), _('December') - ] - for each in self._volre.keys(): - match = re.match(self._volre[each], volname) - # Let ValueErrors percolate up - if match: - year = int(match.group('year')) - if each == 'quarter': - d =["", _("First"), _("Second"), _("Third"), _("Fourth") ] - ord = d[int(match.group('quarter'))] - return _("%(ord)s quarter %(year)i") - elif each == 'month': - monthstr = match.group('month').lower() - for i in range(1, 13): - monthname = time.strftime("%B", (1999,i,1,0,0,0,0,1,0)) - if monthstr.lower() == monthname.lower(): - month = monthdict[i] - return _("%(month)s %(year)i") - raise ValueError, "%s is not a month!" % monthstr - elif each == 'week': - month = monthdict[int(match.group("month"))] - day = int(match.group("day")) - return _("The Week Of Monday %(day)i %(month)s %(year)i") - elif each == 'day': - month = monthdict[int(match.group("month"))] - day = int(match.group("day")) - return _("%(day)i %(month)s %(year)i") - else: - return match.group('year') - raise ValueError, "%s is not a valid volname" % volname - -# The following two methods should be inverses of each other. -ddm - - def dateToVolName(self,date): - datetuple=time.localtime(date) - if self.ARCHIVE_PERIOD=='year': - return time.strftime("%Y",datetuple) - elif self.ARCHIVE_PERIOD=='quarter': - if datetuple[1] in [1,2,3]: - return time.strftime("%Yq1",datetuple) - elif datetuple[1] in [4,5,6]: - return time.strftime("%Yq2",datetuple) - elif datetuple[1] in [7,8,9]: - return time.strftime("%Yq3",datetuple) - else: - return time.strftime("%Yq4",datetuple) - elif self.ARCHIVE_PERIOD == 'day': - return time.strftime("%Y%m%d", datetuple) - elif self.ARCHIVE_PERIOD == 'week': - # Reconstruct "seconds since epoch", and subtract weekday - # multiplied by the number of seconds in a day. - monday = time.mktime(datetuple) - datetuple[6] * 24 * 60 * 60 - # Build a new datetuple from this "seconds since epoch" value - datetuple = time.localtime(monday) - return time.strftime("Week-of-Mon-%Y%m%d", datetuple) - # month. -ddm - else: - return time.strftime("%Y-%B",datetuple) - - - def volNameToDate(self, volname): - volname = volname.strip() - for each in self._volre.keys(): - match = re.match(self._volre[each],volname) - if match: - year = int(match.group('year')) - month = 1 - day = 1 - if each == 'quarter': - q = int(match.group('quarter')) - month = (q * 3) - 2 - elif each == 'month': - monthstr = match.group('month').lower() - m = [] - for i in range(1,13): - m.append( - time.strftime("%B",(1999,i,1,0,0,0,0,1,0)).lower()) - try: - month = m.index(monthstr) + 1 - except ValueError: - pass - elif each == 'week' or each == 'day': - month = int(match.group("month")) - day = int(match.group("day")) - try: - return time.mktime((year,month,1,0,0,0,0,1,-1)) - except OverflowError: - return 0.0 - return 0.0 - - def sortarchives(self): - def sf(a, b): - al = self.volNameToDate(a) - bl = self.volNameToDate(b) - if al > bl: - return 1 - elif al < bl: - return -1 - else: - return 0 - if self.ARCHIVE_PERIOD in ('month','year','quarter'): - self.archives.sort(sf) - else: - self.archives.sort() - self.archives.reverse() - - def message(self, msg): - if self.VERBOSE: - f = sys.stderr - f.write(msg) - if msg[-1:] != '\n': - f.write('\n') - f.flush() - - def open_new_archive(self, archive, archivedir): - index_html = os.path.join(archivedir, 'index.html') - try: - os.unlink(index_html) - except: - pass - os.symlink(self.DEFAULTINDEX+'.html',index_html) - - def write_index_header(self): - self.depth=0 - print self.html_head() - if not self.THREADLAZY and self.type=='Thread': - self.message(_("Computing threaded index\n")) - self.updateThreadedIndex() - - def write_index_footer(self): - for i in range(self.depth): - print '' - print self.html_foot() - - def write_index_entry(self, article): - subject = self.get_header("subject", article) - author = self.get_header("author", article) - if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: - try: - author = re.sub('@', _(' at '), author) - except UnicodeError: - # Non-ASCII author contains '@' ... no valid email anyway - pass - subject = CGIescape(subject, self.lang) - author = CGIescape(author, self.lang) - - d = { - 'filename': urllib.quote(article.filename), - 'subject': subject, - 'sequence': article.sequence, - 'author': author - } - print quick_maketext( - 'archidxentry.html', d, - mlist=self.maillist) - - def get_header(self, field, article): - # if we have no decoded header, return the encoded one - result = article.decoded.get(field) - if result is None: - return getattr(article, field) - # otherwise, the decoded one will be Unicode - return result - - def write_threadindex_entry(self, article, depth): - if depth < 0: - self.message('depth<0') - depth = 0 - if depth > self.THREADLEVELS: - depth = self.THREADLEVELS - if depth < self.depth: - for i in range(self.depth-depth): - print '' - elif depth > self.depth: - for i in range(depth-self.depth): - print '
      ' - print '' % (depth, article.threadKey) - self.depth = depth - self.write_index_entry(article) - - def write_TOC(self): - self.sortarchives() - omask = os.umask(002) - try: - toc = open(os.path.join(self.basedir, 'index.html'), 'w') - finally: - os.umask(omask) - toc.write(self.html_TOC()) - toc.close() - - def write_article(self, index, article, path): - # called by add_article - omask = os.umask(002) - try: - f = open(path, 'w') - finally: - os.umask(omask) - f.write(article.as_html()) - f.close() - - # Write the text article to the text archive. - path = os.path.join(self.basedir, "%s.txt" % index) - omask = os.umask(002) - try: - f = open(path, 'a+') - finally: - os.umask(omask) - f.write(article.as_text()) - f.close() - - def update_archive(self, archive): - self.__super_update_archive(archive) - # only do this if the gzip module was imported globally, and - # gzip'ing was enabled via mm_cfg.GZIP_ARCHIVE_TXT_FILES. See - # above. - if gzip: - archz = None - archt = None - txtfile = os.path.join(self.basedir, '%s.txt' % archive) - gzipfile = os.path.join(self.basedir, '%s.txt.gz' % archive) - oldgzip = os.path.join(self.basedir, '%s.old.txt.gz' % archive) - try: - # open the plain text file - archt = open(txtfile) - except IOError: - return - try: - os.rename(gzipfile, oldgzip) - archz = gzip.open(oldgzip) - except (IOError, RuntimeError, os.error): - pass - try: - ou = os.umask(002) - newz = gzip.open(gzipfile, 'w') - finally: - # XXX why is this a finally? - os.umask(ou) - if archz: - newz.write(archz.read()) - archz.close() - os.unlink(oldgzip) - # XXX do we really need all this in a try/except? - try: - newz.write(archt.read()) - newz.close() - archt.close() - except IOError: - pass - os.unlink(txtfile) - - _skip_attrs = ('maillist', '_lock_file', 'charset') - - def getstate(self): - d={} - for each in self.__dict__.keys(): - if not (each in self._skip_attrs - or each.upper() == each): - d[each] = self.__dict__[each] - return d - - # Add tags around URLs and e-mail addresses. - - def __processbody_URLquote(self, lines): - # XXX a lot to do here: - # 1. use lines directly, rather than source and dest - # 2. make it clearer - # 3. make it faster - # TK: Prepare for unicode obscure. - atmark = _(' at ') - if lines and isinstance(lines[0], types.UnicodeType): - atmark = unicode(atmark, Utils.GetCharSet(self.lang), 'replace') - source = lines[:] - dest = lines - last_line_was_quoted = 0 - for i in xrange(0, len(source)): - Lorig = L = source[i] - prefix = suffix = "" - if L is None: - continue - # Italicise quoted text - if self.IQUOTES: - quoted = quotedpat.match(L) - if quoted is None: - last_line_was_quoted = 0 - else: - quoted = quoted.end(0) - prefix = CGIescape(L[:quoted], self.lang) + '' - suffix = '' - if self.SHOWHTML: - suffix += '
      ' - if not last_line_was_quoted: - prefix = '
      ' + prefix - L = L[quoted:] - last_line_was_quoted = 1 - # Check for an e-mail address - L2 = "" - jr = emailpat.search(L) - kr = urlpat.search(L) - while jr is not None or kr is not None: - if jr == None: - j = -1 - else: - j = jr.start(0) - if kr is None: - k = -1 - else: - k = kr.start(0) - if j != -1 and (j < k or k == -1): - text = jr.group(1) - length = len(text) - if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: - text = re.sub('@', atmark, text) - URL = self.maillist.GetScriptURL( - 'listinfo', absolute=1) - else: - URL = 'mailto:' + text - pos = j - elif k != -1 and (j > k or j == -1): - text = URL = kr.group(1) - length = len(text) - pos = k - else: # j==k - raise ValueError, "j==k: This can't happen!" - #length = len(text) - #self.message("URL: %s %s %s \n" - # % (CGIescape(L[:pos]), URL, CGIescape(text))) - L2 += '%s
      %s' % ( - CGIescape(L[:pos], self.lang), - html_quote(URL), CGIescape(text, self.lang)) - L = L[pos+length:] - jr = emailpat.search(L) - kr = urlpat.search(L) - if jr is None and kr is None: - L = CGIescape(L, self.lang) - L = prefix + L2 + L + suffix - source[i] = None - dest[i] = L - - # Perform Hypermail-style processing of directives - # in message bodies. Lines between and will be written - # out precisely as they are; other lines will be passed to func2 - # for further processing . - - def __processbody_HTML(self, lines): - # XXX need to make this method modify in place - source = lines[:] - dest = lines - l = len(source) - i = 0 - while i < l: - while i < l and htmlpat.match(source[i]) is None: - i = i + 1 - if i < l: - source[i] = None - i = i + 1 - while i < l and nohtmlpat.match(source[i]) is None: - dest[i], source[i] = source[i], None - i = i + 1 - if i < l: - source[i] = None - i = i + 1 - - def format_article(self, article): - # called from add_article - # TBD: Why do the HTML formatting here and keep it in the - # pipermail database? It makes more sense to do the html - # formatting as the article is being written as html and toss - # the data after it has been written to the archive file. - lines = filter(None, article.body) - # Handle directives - if self.ALLOWHTML: - self.__processbody_HTML(lines) - self.__processbody_URLquote(lines) - if not self.SHOWHTML and lines: - lines.insert(0, '
      ')
      -            lines.append('
      ') - else: - # Do fancy formatting here - if self.SHOWBR: - lines = map(lambda x:x + "
      ", lines) - else: - for i in range(0, len(lines)): - s = lines[i] - if s[0:1] in ' \t\n': - lines[i] = '

      ' + s - article.html_body = lines - return article - - def update_article(self, arcdir, article, prev, next): - seq = article.sequence - filename = os.path.join(arcdir, article.filename) - self.message(_('Updating HTML for article %(seq)s')) - try: - f = open(filename) - article.loadbody_fromHTML(f) - f.close() - except IOError, e: - if e.errno <> errno.ENOENT: raise - self.message(_('article file %(filename)s is missing!')) - article.prev = prev - article.next = next - omask = os.umask(002) - try: - f = open(filename, 'w') - finally: - os.umask(omask) - f.write(article.as_html()) - f.close() diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/pipermail.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/pipermail.py deleted file mode 100644 index 8b20b56..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Archiver/pipermail.py +++ /dev/null @@ -1,879 +0,0 @@ -#! /usr/bin/env python - -from __future__ import nested_scopes - -import mailbox -import os -import re -import sys -import time -from email.Utils import parseaddr, parsedate_tz, mktime_tz, formatdate -import cPickle as pickle -from cStringIO import StringIO -from string import lowercase - -__version__ = '0.09 (Mailman edition)' -VERSION = __version__ -CACHESIZE = 100 # Number of slots in the cache - -from Mailman import Errors -from Mailman.Mailbox import ArchiverMailbox -from Mailman.Logging.Syslog import syslog -from Mailman.i18n import _ - -# True/False -try: - True, False -except NameError: - True = 1 - False = 0 - -SPACE = ' ' - - -msgid_pat = re.compile(r'(<.*>)') -def strip_separators(s): - "Remove quotes or parenthesization from a Message-ID string" - if not s: - return "" - if s[0] in '"<([' and s[-1] in '">)]': - s = s[1:-1] - return s - -smallNameParts = ['van', 'von', 'der', 'de'] - -def fixAuthor(author): - "Canonicalize a name into Last, First format" - # If there's a comma, guess that it's already in "Last, First" format - if ',' in author: - return author - L = author.split() - i = len(L) - 1 - if i == 0: - return author # The string's one word--forget it - if author.upper() == author or author.lower() == author: - # Damn, the name is all upper- or lower-case. - while i > 0 and L[i-1].lower() in smallNameParts: - i = i - 1 - else: - # Mixed case; assume that small parts of the last name will be - # in lowercase, and check them against the list. - while i>0 and (L[i-1][0] in lowercase or - L[i-1].lower() in smallNameParts): - i = i - 1 - author = SPACE.join(L[-1:] + L[i:-1]) + ', ' + SPACE.join(L[:i]) - return author - -# Abstract class for databases - -class DatabaseInterface: - def __init__(self): pass - def close(self): pass - def getArticle(self, archive, msgid): pass - def hasArticle(self, archive, msgid): pass - def addArticle(self, archive, article, subject=None, author=None, - date=None): pass - def firstdate(self, archive): pass - def lastdate(self, archive): pass - def first(self, archive, index): pass - def next(self, archive, index): pass - def numArticles(self, archive): pass - def newArchive(self, archive): pass - def setThreadKey(self, archive, key, msgid): pass - def getOldestArticle(self, subject): pass - -class Database(DatabaseInterface): - """Define the basic sorting logic for a database - - Assumes that the database internally uses dateIndex, authorIndex, - etc. - """ - - # TBD Factor out more of the logic shared between BSDDBDatabase - # and HyperDatabase and place it in this class. - - def __init__(self): - # This method need not be called by subclasses that do their - # own initialization. - self.dateIndex = {} - self.authorIndex = {} - self.subjectIndex = {} - self.articleIndex = {} - self.changed = {} - - def addArticle(self, archive, article, subject=None, author=None, - date=None): - # create the keys; always end w/ msgid which will be unique - authorkey = (author or article.author, article.date, - article.msgid) - subjectkey = (subject or article.subject, article.date, - article.msgid) - datekey = date or article.date, article.msgid - - # Add the new article - self.dateIndex[datekey] = article.msgid - self.authorIndex[authorkey] = article.msgid - self.subjectIndex[subjectkey] = article.msgid - - self.store_article(article) - self.changed[archive, article.msgid] = None - - parentID = article.parentID - if parentID is not None and self.articleIndex.has_key(parentID): - parent = self.getArticle(archive, parentID) - myThreadKey = parent.threadKey + article.date + '-' - else: - myThreadKey = article.date + '-' - article.threadKey = myThreadKey - key = myThreadKey, article.msgid - self.setThreadKey(archive, key, article.msgid) - - def store_article(self, article): - """Store article without message body to save space""" - # TBD this is not thread safe! - temp = article.body - temp2 = article.html_body - article.body = [] - del article.html_body - self.articleIndex[article.msgid] = pickle.dumps(article) - article.body = temp - article.html_body = temp2 - - -# The Article class encapsulates a single posting. The attributes -# are: -# -# sequence : Sequence number, unique for each article in a set of archives -# subject : Subject -# datestr : The posting date, in human-readable format -# date : The posting date, in purely numeric format -# headers : Any other headers of interest -# author : The author's name (and possibly organization) -# email : The author's e-mail address -# msgid : A unique message ID -# in_reply_to: If != "", this is the msgid of the article being replied to -# references : A (possibly empty) list of msgid's of earlier articles -# in the thread -# body : A list of strings making up the message body - -class Article: - _last_article_time = time.time() - - def __init__(self, message = None, sequence = 0, keepHeaders = []): - if message is None: - return - self.sequence = sequence - - self.parentID = None - self.threadKey = None - # otherwise the current sequence number is used. - id = strip_separators(message['Message-Id']) - if id == "": - self.msgid = str(self.sequence) - else: self.msgid = id - - if message.has_key('Subject'): - self.subject = str(message['Subject']) - else: - self.subject = _('No subject') - if self.subject == "": self.subject = _('No subject') - - self._set_date(message) - - # Figure out the e-mail address and poster's name. Use the From: - # field first, followed by Reply-To: - self.author, self.email = parseaddr(message.get('From', '')) - e = message['Reply-To'] - if not self.email and e is not None: - ignoreauthor, self.email = parseaddr(e) - self.email = strip_separators(self.email) - self.author = strip_separators(self.author) - - if self.author == "": - self.author = self.email - - # Save the In-Reply-To:, References:, and Message-ID: lines - # - # TBD: The original code does some munging on these fields, which - # shouldn't be necessary, but changing this may break code. For - # safety, I save the original headers on different attributes for use - # in writing the plain text periodic flat files. - self._in_reply_to = message['in-reply-to'] - self._references = message['references'] - self._message_id = message['message-id'] - - i_r_t = message['In-Reply-To'] - if i_r_t is None: - self.in_reply_to = '' - else: - match = msgid_pat.search(i_r_t) - if match is None: self.in_reply_to = '' - else: self.in_reply_to = strip_separators(match.group(1)) - - references = message['References'] - if references is None: - self.references = [] - else: - self.references = map(strip_separators, references.split()) - - # Save any other interesting headers - self.headers = {} - for i in keepHeaders: - if message.has_key(i): - self.headers[i] = message[i] - - # Read the message body - s = StringIO(message.get_payload(decode=True)\ - or message.as_string().split('\n\n',1)[1]) - self.body = s.readlines() - - def _set_date(self, message): - def floatdate(header): - missing = [] - datestr = message.get(header, missing) - if datestr is missing: - return None - date = parsedate_tz(datestr) - try: - return mktime_tz(date) - except (TypeError, ValueError, OverflowError): - return None - date = floatdate('date') - if date is None: - date = floatdate('x-list-received-date') - if date is None: - # What's left to try? - date = self._last_article_time + 1 - self._last_article_time = date - self.date = '%011i' % date - self.datestr = message.get('date') \ - or message.get('x-list-received-date') \ - or formatdate(date) - - def __repr__(self): - return '

      ' - - def finished_update_article(self): - pass - -# Pipermail formatter class - -class T: - DIRMODE = 0755 # Mode to give to created directories - FILEMODE = 0644 # Mode to give to created files - INDEX_EXT = ".html" # Extension for indexes - - def __init__(self, basedir = None, reload = 1, database = None): - # If basedir isn't provided, assume the current directory - if basedir is None: - self.basedir = os.getcwd() - else: - basedir = os.path.expanduser(basedir) - self.basedir = basedir - self.database = database - - # If the directory doesn't exist, create it. This code shouldn't get - # run anymore, we create the directory in Archiver.py. It should only - # get used by legacy lists created that are only receiving their first - # message in the HTML archive now -- Marc - try: - os.stat(self.basedir) - except os.error, errdata: - errno, errmsg = errdata - if errno != 2: - raise os.error, errdata - else: - self.message(_('Creating archive directory ') + self.basedir) - omask = os.umask(0) - try: - os.mkdir(self.basedir, self.DIRMODE) - finally: - os.umask(omask) - - # Try to load previously pickled state - try: - if not reload: - raise IOError - f = open(os.path.join(self.basedir, 'pipermail.pck'), 'r') - self.message(_('Reloading pickled archive state')) - d = pickle.load(f) - f.close() - for key, value in d.items(): - setattr(self, key, value) - except (IOError, EOFError): - # No pickled version, so initialize various attributes - self.archives = [] # Archives - self._dirty_archives = [] # Archives that will have to be updated - self.sequence = 0 # Sequence variable used for - # numbering articles - self.update_TOC = 0 # Does the TOC need updating? - # - # make the basedir variable work when passed in as an __init__ arg - # and different from the one in the pickle. Let the one passed in - # as an __init__ arg take precedence if it's stated. This way, an - # archive can be moved from one place to another and still work. - # - if basedir != self.basedir: - self.basedir = basedir - - def close(self): - "Close an archive, save its state, and update any changed archives." - self.update_dirty_archives() - self.update_TOC = 0 - self.write_TOC() - # Save the collective state - self.message(_('Pickling archive state into ') - + os.path.join(self.basedir, 'pipermail.pck')) - self.database.close() - del self.database - - omask = os.umask(007) - try: - f = open(os.path.join(self.basedir, 'pipermail.pck'), 'w') - finally: - os.umask(omask) - pickle.dump(self.getstate(), f) - f.close() - - def getstate(self): - # can override this in subclass - return self.__dict__ - - # - # Private methods - # - # These will be neither overridden nor called by custom archivers. - # - - - # Create a dictionary of various parameters that will be passed - # to the write_index_{header,footer} functions - def __set_parameters(self, archive): - # Determine the earliest and latest date in the archive - firstdate = self.database.firstdate(archive) - lastdate = self.database.lastdate(archive) - - # Get the current time - now = time.asctime(time.localtime(time.time())) - self.firstdate = firstdate - self.lastdate = lastdate - self.archivedate = now - self.size = self.database.numArticles(archive) - self.archive = archive - self.version = __version__ - - # Find the message ID of an article's parent, or return None - # if no parent can be found. - - def __findParent(self, article, children = []): - parentID = None - if article.in_reply_to: - parentID = article.in_reply_to - elif article.references: - # Remove article IDs that aren't in the archive - refs = filter(self.articleIndex.has_key, article.references) - if not refs: - return None - maxdate = self.database.getArticle(self.archive, - refs[0]) - for ref in refs[1:]: - a = self.database.getArticle(self.archive, ref) - if a.date > maxdate.date: - maxdate = a - parentID = maxdate.msgid - else: - # Look for the oldest matching subject - try: - key, tempid = \ - self.subjectIndex.set_location(article.subject) - print key, tempid - self.subjectIndex.next() - [subject, date] = key.split('\0') - print article.subject, subject, date - if subject == article.subject and tempid not in children: - parentID = tempid - except KeyError: - pass - return parentID - - # Update the threaded index completely - def updateThreadedIndex(self): - # Erase the threaded index - self.database.clearIndex(self.archive, 'thread') - - # Loop over all the articles - msgid = self.database.first(self.archive, 'date') - while msgid is not None: - try: - article = self.database.getArticle(self.archive, msgid) - except KeyError: - pass - else: - if article.parentID is None or \ - not self.database.hasArticle(self.archive, - article.parentID): - # then - pass - else: - parent = self.database.getArticle(self.archive, - article.parentID) - article.threadKey = parent.threadKey+article.date+'-' - self.database.setThreadKey(self.archive, - (article.threadKey, article.msgid), - msgid) - msgid = self.database.next(self.archive, 'date') - - # - # Public methods: - # - # These are part of the public interface of the T class, but will - # never be overridden (unless you're trying to do something very new). - - # Update a single archive's indices, whether the archive's been - # dirtied or not. - def update_archive(self, archive): - self.archive = archive - self.message(_("Updating index files for archive [%(archive)s]")) - arcdir = os.path.join(self.basedir, archive) - self.__set_parameters(archive) - - for hdr in ('Date', 'Subject', 'Author'): - self._update_simple_index(hdr, archive, arcdir) - - self._update_thread_index(archive, arcdir) - - def _update_simple_index(self, hdr, archive, arcdir): - self.message(" " + hdr) - self.type = hdr - hdr = hdr.lower() - - self._open_index_file_as_stdout(arcdir, hdr) - self.write_index_header() - count = 0 - # Loop over the index entries - msgid = self.database.first(archive, hdr) - while msgid is not None: - try: - article = self.database.getArticle(self.archive, msgid) - except KeyError: - pass - else: - count = count + 1 - self.write_index_entry(article) - msgid = self.database.next(archive, hdr) - # Finish up this index - self.write_index_footer() - self._restore_stdout() - - def _update_thread_index(self, archive, arcdir): - self.message(_(" Thread")) - self._open_index_file_as_stdout(arcdir, "thread") - self.type = 'Thread' - self.write_index_header() - - # To handle the prev./next in thread pointers, we need to - # track articles 5 at a time. - - # Get the first 5 articles - L = [None] * 5 - i = 2 - msgid = self.database.first(self.archive, 'thread') - - while msgid is not None and i < 5: - L[i] = self.database.getArticle(self.archive, msgid) - i = i + 1 - msgid = self.database.next(self.archive, 'thread') - - while L[2] is not None: - article = L[2] - artkey = None - if article is not None: - artkey = article.threadKey - if artkey is not None: - self.write_threadindex_entry(article, artkey.count('-') - 1) - if self.database.changed.has_key((archive,article.msgid)): - a1 = L[1] - a3 = L[3] - self.update_article(arcdir, article, a1, a3) - if a3 is not None: - self.database.changed[(archive, a3.msgid)] = None - if a1 is not None: - key = archive, a1.msgid - if not self.database.changed.has_key(key): - self.update_article(arcdir, a1, L[0], L[2]) - else: - del self.database.changed[key] - if L[0]: - L[0].finished_update_article() - L = L[1:] # Rotate the list - if msgid is None: - L.append(msgid) - else: - L.append(self.database.getArticle(self.archive, msgid)) - msgid = self.database.next(self.archive, 'thread') - - self.write_index_footer() - self._restore_stdout() - - def _open_index_file_as_stdout(self, arcdir, index_name): - path = os.path.join(arcdir, index_name + self.INDEX_EXT) - omask = os.umask(002) - try: - self.__f = open(path, 'w') - finally: - os.umask(omask) - self.__stdout = sys.stdout - sys.stdout = self.__f - - def _restore_stdout(self): - sys.stdout = self.__stdout - self.__f.close() - del self.__f - del self.__stdout - - # Update only archives that have been marked as "changed". - def update_dirty_archives(self): - for i in self._dirty_archives: - self.update_archive(i) - self._dirty_archives = [] - - # Read a Unix mailbox file from the file object , - # and create a series of Article objects. Each article - # object will then be archived. - - def _makeArticle(self, msg, sequence): - return Article(msg, sequence) - - def processUnixMailbox(self, input, start=None, end=None): - mbox = ArchiverMailbox(input, self.maillist) - if start is None: - start = 0 - counter = 0 - while counter < start: - try: - m = mbox.next() - except Errors.DiscardMessage: - continue - if m is None: - return - counter += 1 - while 1: - try: - pos = input.tell() - m = mbox.next() - except Errors.DiscardMessage: - continue - except Exception: - syslog('error', 'uncaught archiver exception at filepos: %s', - pos) - raise - if m is None: - break - if m == '': - # It was an unparseable message - continue - msgid = m.get('message-id', 'n/a') - self.message(_('#%(counter)05d %(msgid)s')) - a = self._makeArticle(m, self.sequence) - self.sequence += 1 - self.add_article(a) - if end is not None and counter >= end: - break - counter += 1 - - def new_archive(self, archive, archivedir): - self.archives.append(archive) - self.update_TOC = 1 - self.database.newArchive(archive) - # If the archive directory doesn't exist, create it - try: - os.stat(archivedir) - except os.error, errdata: - errno, errmsg = errdata - if errno == 2: - omask = os.umask(0) - try: - os.mkdir(archivedir, self.DIRMODE) - finally: - os.umask(omask) - else: - raise os.error, errdata - self.open_new_archive(archive, archivedir) - - def add_article(self, article): - archives = self.get_archives(article) - if not archives: - return - if type(archives) == type(''): - archives = [archives] - - article.filename = filename = self.get_filename(article) - temp = self.format_article(article) - for arch in archives: - self.archive = arch # why do this??? - archivedir = os.path.join(self.basedir, arch) - if arch not in self.archives: - self.new_archive(arch, archivedir) - - # Write the HTML-ized article - self.write_article(arch, temp, os.path.join(archivedir, - filename)) - - if article.decoded.has_key('author'): - author = fixAuthor(article.decoded['author']) - else: - author = fixAuthor(article.author) - if article.decoded.has_key('stripped'): - subject = article.decoded['stripped'].lower() - else: - subject = article.subject.lower() - - article.parentID = parentID = self.get_parent_info(arch, article) - if parentID: - parent = self.database.getArticle(arch, parentID) - article.threadKey = parent.threadKey + article.date + '-' - else: - article.threadKey = article.date + '-' - key = article.threadKey, article.msgid - - self.database.setThreadKey(arch, key, article.msgid) - self.database.addArticle(arch, temp, author=author, - subject=subject) - - if arch not in self._dirty_archives: - self._dirty_archives.append(arch) - - def get_parent_info(self, archive, article): - parentID = None - if article.in_reply_to: - parentID = article.in_reply_to - elif article.references: - refs = self._remove_external_references(article.references) - if refs: - maxdate = self.database.getArticle(archive, refs[0]) - for ref in refs[1:]: - a = self.database.getArticle(archive, ref) - if a.date > maxdate.date: - maxdate = a - parentID = maxdate.msgid - else: - # Get the oldest article with a matching subject, and - # assume this is a follow-up to that article - parentID = self.database.getOldestArticle(archive, - article.subject) - - if parentID and not self.database.hasArticle(archive, parentID): - parentID = None - return parentID - - def write_article(self, index, article, path): - omask = os.umask(002) - try: - f = open(path, 'w') - finally: - os.umask(omask) - temp_stdout, sys.stdout = sys.stdout, f - self.write_article_header(article) - sys.stdout.writelines(article.body) - self.write_article_footer(article) - sys.stdout = temp_stdout - f.close() - - def _remove_external_references(self, refs): - keep = [] - for ref in refs: - if self.database.hasArticle(self.archive, ref): - keep.append(ref) - return keep - - # Abstract methods: these will need to be overridden by subclasses - # before anything useful can be done. - - def get_filename(self, article): - pass - def get_archives(self, article): - """Return a list of indexes where the article should be filed. - A string can be returned if the list only contains one entry, - and the empty list is legal.""" - pass - def format_article(self, article): - pass - def write_index_header(self): - pass - def write_index_footer(self): - pass - def write_index_entry(self, article): - pass - def write_threadindex_entry(self, article, depth): - pass - def write_article_header(self, article): - pass - def write_article_footer(self, article): - pass - def write_article_entry(self, article): - pass - def update_article(self, archivedir, article, prev, next): - pass - def write_TOC(self): - pass - def open_new_archive(self, archive, dir): - pass - def message(self, msg): - pass - - -class BSDDBdatabase(Database): - __super_addArticle = Database.addArticle - - def __init__(self, basedir): - self.__cachekeys = [] - self.__cachedict = {} - self.__currentOpenArchive = None # The currently open indices - self.basedir = os.path.expanduser(basedir) - self.changed = {} # Recently added articles, indexed only by - # message ID - - def firstdate(self, archive): - self.__openIndices(archive) - date = 'None' - try: - date, msgid = self.dateIndex.first() - date = time.asctime(time.localtime(float(date))) - except KeyError: - pass - return date - - def lastdate(self, archive): - self.__openIndices(archive) - date = 'None' - try: - date, msgid = self.dateIndex.last() - date = time.asctime(time.localtime(float(date))) - except KeyError: - pass - return date - - def numArticles(self, archive): - self.__openIndices(archive) - return len(self.dateIndex) - - def addArticle(self, archive, article, subject=None, author=None, - date=None): - self.__openIndices(archive) - self.__super_addArticle(archive, article, subject, author, date) - - # Open the BSDDB files that are being used as indices - # (dateIndex, authorIndex, subjectIndex, articleIndex) - def __openIndices(self, archive): - if self.__currentOpenArchive == archive: - return - - import bsddb - self.__closeIndices() - arcdir = os.path.join(self.basedir, 'database') - omask = os.umask(0) - try: - try: - os.mkdir(arcdir, 02775) - except OSError: - # BAW: Hmm... - pass - finally: - os.umask(omask) - for hdr in ('date', 'author', 'subject', 'article', 'thread'): - path = os.path.join(arcdir, archive + '-' + hdr) - t = bsddb.btopen(path, 'c') - setattr(self, hdr + 'Index', t) - self.__currentOpenArchive = archive - - # Close the BSDDB files that are being used as indices (if they're - # open--this is safe to call if they're already closed) - def __closeIndices(self): - if self.__currentOpenArchive is not None: - pass - for hdr in ('date', 'author', 'subject', 'thread', 'article'): - attr = hdr + 'Index' - if hasattr(self, attr): - index = getattr(self, attr) - if hdr == 'article': - if not hasattr(self, 'archive_length'): - self.archive_length = {} - self.archive_length[self.__currentOpenArchive] = len(index) - index.close() - delattr(self,attr) - self.__currentOpenArchive = None - - def close(self): - self.__closeIndices() - def hasArticle(self, archive, msgid): - self.__openIndices(archive) - return self.articleIndex.has_key(msgid) - def setThreadKey(self, archive, key, msgid): - self.__openIndices(archive) - self.threadIndex[key] = msgid - def getArticle(self, archive, msgid): - self.__openIndices(archive) - if self.__cachedict.has_key(msgid): - self.__cachekeys.remove(msgid) - self.__cachekeys.append(msgid) - return self.__cachedict[msgid] - if len(self.__cachekeys) == CACHESIZE: - delkey, self.__cachekeys = (self.__cachekeys[0], - self.__cachekeys[1:]) - del self.__cachedict[delkey] - s = self.articleIndex[msgid] - article = pickle.loads(s) - self.__cachekeys.append(msgid) - self.__cachedict[msgid] = article - return article - - def first(self, archive, index): - self.__openIndices(archive) - index = getattr(self, index+'Index') - try: - key, msgid = index.first() - return msgid - except KeyError: - return None - def next(self, archive, index): - self.__openIndices(archive) - index = getattr(self, index+'Index') - try: - key, msgid = index.next() - except KeyError: - return None - else: - return msgid - - def getOldestArticle(self, archive, subject): - self.__openIndices(archive) - subject = subject.lower() - try: - key, tempid = self.subjectIndex.set_location(subject) - self.subjectIndex.next() - [subject2, date] = key.split('\0') - if subject != subject2: - return None - return tempid - except KeyError: # XXX what line raises the KeyError? - return None - - def newArchive(self, archive): - pass - - def clearIndex(self, archive, index): - self.__openIndices(archive) - index = getattr(self, index+'Index') - finished = 0 - try: - key, msgid = self.threadIndex.first() - except KeyError: - finished = 1 - while not finished: - del self.threadIndex[key] - try: - key, msgid = self.threadIndex.next() - except KeyError: - finished = 1 - - diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/Auth.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/Auth.py deleted file mode 100755 index 9e9f908..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/Auth.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (C) 1998,1999,2000,2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Common routines for logging in and logging out of the list administrator -and list moderator interface. -""" - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import Errors -from Mailman.htmlformat import Link -from Mailman.htmlformat import HTMLFormatObject - -from Mailman.i18n import _ - - - -class NotLoggedInError(Exception): - """Exception raised when no matching admin cookie was found.""" - def __init__(self, message): - Exception.__init__(self, message) - self.message = message - - - -def loginpage(mlist, scriptname, msg='', frontpage=None): - url = mlist.GetScriptURL(scriptname) - if frontpage: - actionurl = url - else: - actionurl = Utils.GetRequestURI(url) - # if msg: - # The format of msg comes from where it was originated. This way - # the msg can come with more than one design (ej. success or - # error). This is just a consideration because it seems that - # here the msg's value always be error design (failed logins in - # this case). - # msg = ... - if scriptname == 'admindb': - who = _('Moderator') - else: - who = _('Administrator') - # Language stuff - charset = Utils.GetCharSet(mlist.preferred_language) - print 'Content-type: text/html; charset=' + charset + '\n\n' - print Utils.maketext( - 'admlogin.html', - {'listname': mlist.real_name, - 'path' : actionurl, - 'message' : msg, - 'who' : who, - # Links on header section (errormsg) - 'listadmin_link': Link(Utils.ScriptURL('admin'), _('Administration')).Format(), - 'listinfo_link': Link(Utils.ScriptURL('listinfo'), _('General Information')).Format(), - 'errormsg_header': _('Mailing Lists'), - }, mlist=mlist) - print mlist.GetMailmanFooter() - # We need to close some tags at this point - print '' + '\n' + '' diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/__init__.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/__init__.py deleted file mode 100755 index f569e43..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (C) 1998,1999,2000,2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/admin.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/admin.py deleted file mode 100755 index 1243250..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/admin.py +++ /dev/null @@ -1,1482 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Process and produce the list-administration options forms.""" - -# For Python 2.1.x compatibility -from __future__ import nested_scopes - -import sys -import os -import re -import cgi -import sha -import urllib -import signal -from types import * -from string import lowercase, digits - -from email.Utils import unquote, parseaddr, formataddr - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MailList -from Mailman import Errors -from Mailman import MemberAdaptor -from Mailman import i18n -from Mailman.UserDesc import UserDesc -from Mailman.htmlformat import * -from Mailman.Cgi import Auth -from Mailman.Logging.Syslog import syslog - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - -NL = '\n' -OPTCOLUMNS = 11 - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -def main(): - # Try to find out which list is being administered - parts = Utils.GetPathPieces() - if not parts: - # None, so just do the admin overview and be done with it - admin_overview() - return - # Get the list object - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - admin_overview(_('No such list %(safelistname)s')) - syslog('error', 'admin.py access for non-existent list: %s', - listname) - return - # Now that we know what list has been requested, all subsequent admin - # pages are shown in that list's preferred language. - i18n.set_language(mlist.preferred_language) - # If the user is not authenticated, we're done. - cgidata = cgi.FieldStorage(keep_blank_values=1) - - if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - cgidata.getvalue('adminpw', '')): - if cgidata.has_key('adminpw'): - # This is a re-authorization attempt - msg = Div(Paragraph(_('Authorization failed.'))).Format(css='class="message error strong"') - else: - msg = '' - Auth.loginpage(mlist, 'admin', msg=msg) - return - - # Which subcategory was requested? Default is `general' - if len(parts) == 1: - category = 'general' - subcat = None - elif len(parts) == 2: - category = parts[1] - subcat = None - else: - category = parts[1] - subcat = parts[2] - - # Is this a log-out request? - if category == 'logout': - print mlist.ZapCookie(mm_cfg.AuthListAdmin) - Auth.loginpage(mlist, 'admin', frontpage=1) - return - - # Sanity check - if category not in mlist.GetConfigCategories().keys(): - category = 'general' - - # Is the request for variable details? - varhelp = None - qsenviron = os.environ.get('QUERY_STRING') - parsedqs = None - if qsenviron: - parsedqs = cgi.parse_qs(qsenviron) - if cgidata.has_key('VARHELP'): - varhelp = cgidata.getvalue('VARHELP') - elif parsedqs: - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = parsedqs.get('VARHELP') - if qs and isinstance(qs, ListType): - varhelp = qs[0] - if varhelp: - option_help(mlist, varhelp) - return - - # The html page document - doc = Document() - doc.set_language(mlist.preferred_language) - - # From this point on, the MailList object must be locked. However, we - # must release the lock no matter how we exit. try/finally isn't enough, - # because of this scenario: user hits the admin page which may take a long - # time to render; user gets bored and hits the browser's STOP button; - # browser shuts down socket; server tries to write to broken socket and - # gets a SIGPIPE. Under Apache 1.3/mod_cgi, Apache catches this SIGPIPE - # (I presume it is buffering output from the cgi script), then turns - # around and SIGTERMs the cgi process. Apache waits three seconds and - # then SIGKILLs the cgi process. We /must/ catch the SIGTERM and do the - # most reasonable thing we can in as short a time period as possible. If - # we get the SIGKILL we're screwed (because it's uncatchable and we'll - # have no opportunity to clean up after ourselves). - # - # This signal handler catches the SIGTERM, unlocks the list, and then - # exits the process. The effect of this is that the changes made to the - # MailList object will be aborted, which seems like the only sensible - # semantics. - # - # BAW: This may not be portable to other web servers or cgi execution - # models. - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - mlist.Unlock() - # ...and ensure we exit, otherwise race conditions could cause us to - # enter MailList.Save() while we're in the unlocked state, and that - # could be bad! - sys.exit(0) - - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) - - if cgidata.keys(): - # There are options to change - change_options(mlist, category, subcat, cgidata, doc) - # Let the list sanity check the changed values - mlist.CheckValues() - # Additional sanity checks - if not mlist.digestable and not mlist.nondigestable: - doc.addError( - _('''You have turned off delivery of both digest and non-digest messages. This is an incompatible state of affairs. You must turn on either digest delivery or non-digest delivery or your mailing list will basically be unusable.'''), tag=_('Warning: ')) - - if not mlist.digestable and mlist.getDigestMemberKeys(): - doc.addError( - _('''You have digest members, but digests are turned off. Those people will not receive mail.'''), - tag=_('Warning: ')) - if not mlist.nondigestable and mlist.getRegularMemberKeys(): - doc.addError( - _('''You have regular list members but non-digestified mail is turned off. They will receive mail until you fix this problem.'''), tag=_('Warning: ')) - # Glom up the results page and print it out - show_results(mlist, doc, category, subcat, cgidata) - print doc.Format() - mlist.Save() - finally: - # Now be sure to unlock the list. It's okay if we get a signal here - # because essentially, the signal handler will do the same thing. And - # unlocking is unconditional, so it's not an error if we unlock while - # we're already unlocked. - mlist.Unlock() - - - -def admin_overview(msg=''): - # Show the administrative overview page, with the list of all the lists on - # this host. msg is an optional error message to display at the top of - # the page. - # - # This page should be displayed in the server's default language, which - # should have already been set. - hostname = Utils.get_domain() - title = _('Administration') + ' - ' + _('Mailing Lists') - # The html `document' - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.SetTitle(title) - # The table that will hold everything - table = Table() - # Skip any mailing list that isn't advertised. - advertised = [] - listnames = Utils.list_names() - listnames.sort() - - for name in listnames: - mlist = MailList.MailList(name, lock=0) - if mlist.advertised: - if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ - mlist.web_page_url.find(hostname) == -1: - # List is for different identity of this host - skip it. - continue - else: - advertised.append((mlist.GetScriptURL('admin'), - mlist.real_name, - mlist.description)) - # Greeting depends on whether there was an error or not - #if msg: - # greeting = FontAttr(msg, color="ff5060", size="+1") - #else: - # greeting = _("Welcome!") - - welcome = Header(1, _('Administration')).Format() - mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() - if not advertised: - welcome += Paragraph( - _('''There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.''')).Format(css='class="strong"') - else: - welcome += Paragraph( - _('''Below is the collection of publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s. Click on a list name to visit the configuration pages for that list.''')).Format() - - creatorurl = Utils.ScriptURL('create') - mailman_owner = Utils.get_site_email() - extra = msg and _('right ') or '' - welcome += Paragraph( - _('''To visit the administrators configuration page for an unadvertised list, open a URL similar to this one, but with a '/' and the %(extra)slist name appended. If you have the proper authority, you can also create a new mailing list.''')).Format() - - welcome += Paragraph( - _('''General list information can be found at ''') + - Link(Utils.ScriptURL('listinfo'), - _('the mailing list overview page')).Format() + '.').Format() - - welcome += Paragraph(_('(Send questions and comments to ') + - Link('mailto:%s' % mailman_owner, mailman_owner).Format() + - '.)').Format() - - if advertised: - highlight = 1 - for url, real_name, description in advertised: - table.AddRow( - [Link(url, real_name), - description or _('[no description available]')]) - - if highlight: - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="title strong"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="title left"') - else: - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="strong"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - highlight = not highlight - - doc.AddItem(welcome) - # When printing the mailing list table; avoid empty tag to appear when - # no mailing list / rows are present inside it. Empty
      tags are a violation - # in the "-//W3C//DTD XHTML 1.0 Transitional//EN" standard. - if advertised: - doc.AddItem(table) - doc.AddItem(MailmanLogo()) - print doc.Format() - - - -def option_help(mlist, varhelp): - # The html page document - doc = Document() - doc.set_language(mlist.preferred_language) - # Find out which category and variable help is being requested for. - item = None - reflist = varhelp.split('/') - if len(reflist) >= 2: - category = subcat = None - if len(reflist) == 2: - category, varname = reflist - elif len(reflist) == 3: - category, subcat, varname = reflist - options = mlist.GetConfigInfo(category, subcat) - for i in options: - if i and i[0] == varname: - item = i - break - # Print an error message if we couldn't find a valid one - if not item: - bad = _('No valid variable name found.') - doc.addError(bad) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - # Get the details about the variable - varname, kind, params, dependancies, description, elaboration = \ - get_item_characteristics(item) - # Set up the document - realname = mlist.real_name - doc.SetTitle(_("Mailman %(varname)s List Option Help")) - doc.AddItem(Header(1, _('%(realname)s Mailing list Configuration Help')).Format()) - doc.AddItem(Header(2, _('%(varname)s Option')).Format()) - doc.AddItem(Paragraph(_("%s (%s): %s" % (varname, category, description))).Format()) - - if elaboration: - doc.AddItem(Paragraph("%s" % elaboration).Format()) - - if subcat: - url = '%s/%s/%s' % (mlist.GetScriptURL('admin'), category, subcat) - else: - url = '%s/%s' % (mlist.GetScriptURL('admin'), category) - - form = Form(url) - valtab = Table() - add_options_table_item(mlist, category, subcat, valtab, item, detailsp=0) - form.AddItem(valtab) - form.AddItem(submit_button()) - doc.AddItem(form) - - adminurl = mlist.GetScriptURL('admin') - - if subcat: - url = '%s/%s/%s' % (adminurl, category, subcat) - else: - url = '%s/%s' % (adminurl, category) - - categoryname = mlist.GetConfigCategories()[category][0] - doc.AddItem(Paragraph( - _('''Warning: changing this option here could cause other screens to be out-of-sync. Be sure to reload any other pages that are displaying this option for this mailing list. You can also ''') - + Link(url, _('return to the %(categoryname)s options page')).Format() - + '.')) - - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def show_results(mlist, doc, category, subcat, cgidata): - # Produce the results page - adminurl = mlist.GetScriptURL('admin') - categories = mlist.GetConfigCategories() - label = _(categories[category][0]) - - # Set up the document's headers - realname = mlist.real_name - doc.SetTitle(_('%(realname)s Administration (%(label)s)')) - doc.AddItem(Header(1, _('%(realname)s mailing list administration')).Format()) - # Now we need to craft the form that will be submitted, which will contain - # all the variable settings, etc. This is a bit of a kludge because we - # know that the autoreply and members categories supports file uploads. - encoding = None - if category in ('autoreply', 'members'): - encoding = 'multipart/form-data' - if subcat: - form = Form('%s/%s/%s' % (adminurl, category, subcat), - encoding=encoding) - else: - form = Form('%s/%s' % (adminurl, category), encoding=encoding) - # The `other links' are stuff in the right column. - otherlinks = UnorderedList() - otherlinks.AddItem(Link(mlist.GetScriptURL('admindb'), - _('Pending moderator requests'))) - otherlinks.AddItem(Link(mlist.GetScriptURL('listinfo'), - _('General list information'))) - otherlinks.AddItem(Link(mlist.GetScriptURL('edithtml'), - _('Public Templates'))) - otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(), - _('List archives')).Format()) - # We do not allow through-the-web deletion of the site list! - if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and \ - mlist.internal_name() <> mm_cfg.MAILMAN_SITE_LIST: - otherlinks.AddItem(Link(mlist.GetScriptURL('rmlist'), - _('Delete this mailing list')).Format() + - _(' (requires confirmation)')) - otherlinks.AddItem(Link('%s/logout' % adminurl, - # BAW: What I really want is a blank line, but - # adding an   won't do it because of the - # bullet added to the list item. - '%s' % _('Logout'))) - # These are links to other categories and live in the left column - categorylinks_1 = categorylinks = UnorderedList() - categorylinks_2 = '' - categorykeys = categories.keys() - half = len(categorykeys) / 2 - counter = 0 - subcat = None - for k in categorykeys: - label = _(categories[k][0]) - url = '%s/%s' % (adminurl, k) - if k == category: - # Handle subcategories - subcats = mlist.GetConfigSubCategories(k) - if subcats: - subcat = Utils.GetPathPieces()[-1] - for k, v in subcats: - if k == subcat: - break - else: - # The first subcategory in the list is the default - subcat = subcats[0][0] - subcat_items = [] - for sub, text in subcats: - if sub == subcat: - text = Bold('[%s]' % text).Format() - subcat_items.append(Link(url + '/' + sub, text)) - categorylinks.AddItem( - Bold(label).Format() + - UnorderedList(*subcat_items).Format()) - else: - categorylinks.AddItem(Link(url, Bold('[%s]' % label))) - else: - categorylinks.AddItem(Link(url, label)) - counter += 1 - if counter >= half: - categorylinks_2 = categorylinks = UnorderedList() - counter = -len(categorykeys) - # ...and add the links to the document. - form.AddItem('
      ') - form.AddItem('

      ' + _('Other options') + '

      ') - form.AddItem(otherlinks.Format(css='class="adminpanel"')) - form.AddItem('
      ') - form.AddItem('
      ') - form.AddItem('

      ' + _('Configuration options') + '

      ') - form.AddItem(categorylinks_1.Format(css='class="adminpanel floatl"')) - form.AddItem(categorylinks_2.Format(css='class="adminpanel floatl"')) - form.AddItem('
      ') - # Make the emergency stop switch a rude solo light - if mlist.emergency: - label = _('Emergency moderation of all list traffic is enabled') - form.AddItem(Paragraph(Link('?VARHELP=general/emergency', label)).Format(css='class="emergency"')) - form.AddItem('
      ') - form.AddItem(Paragraph(_('''Make your changes in the following section, then submit them using the Submit Your Changes button below.''')).Format()) - - # The members and passwords categories are special in that they aren't - # defined in terms of gui elements. Create those pages here. - if category == 'members': - - # Figure out which subcategory we should display - subcat = Utils.GetPathPieces()[-1] - if subcat not in ('list', 'add', 'remove'): - subcat = 'list' - - # Add member category specific tables - form.AddItem(membership_options(mlist, subcat, cgidata, doc, form)) - form.AddItem(submit_button('setmemberopts_btn')) - - # In "list" subcategory, we can also search for members - if subcat == 'list': - table = Table() - container = Container() - container.AddItem(Header(2, _('Additional Member Tasks')).Format()) - - # Add a section to set the moderation bit for all members - table.AddRow([ - _("""Set everyone's moderation bit, including those members not currently visible:"""), - RadioButtonArray('allmodbit_val', - (_('Off'), _('On')), - mlist.default_member_moderation)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - table.AddRow([SubmitButton('allmodbit_btn', _('Set'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') - - form.AddItem(container) - form.AddItem(table) - - elif category == 'passwords': - form.AddItem(password_inputs(mlist)) - form.AddItem(submit_button()) - else: - form.AddItem(show_variables(mlist, category, subcat, cgidata, doc)) - form.AddItem(submit_button()) - # And add the form - doc.AddItem(form) - doc.AddItem(mlist.GetMailmanFooter()) - - - -def show_variables(mlist, category, subcat, cgidata, doc): - options = mlist.GetConfigInfo(category, subcat) - - # The table containing the results - table = Table() - - # Get and portray the text label for the category. - categories = mlist.GetConfigCategories() - label = _(categories[category][0]) - - container = Container() - container.AddItem(Header(2, label).Format()) - - # The very first item in the config info will be treated as a general - # description if it is a string - description = options[0] - if isinstance(description, StringType): - container.AddItem(description) - options = options[1:] - - if not options: - return container - - # Add the global column headers - table.AddRow([_('Description:'), - _('Value')]) - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, css='class="description center strong"') - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 1, css='class="value center strong"') - - for item in options: - if type(item) == StringType: - # The very first banner option (string in an options list) is - # treated as a general description, while any others are - # treated as section headers - centered and italicized... - table.AddRow([Header(3, item).Format(css='class="center"')]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - else: - add_options_table_item(mlist, category, subcat, table, item) - - container.AddItem(table) - return container - - - -def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): - # Add a row to an options table with the item description and value. - varname, kind, params, extra, descr, elaboration = \ - get_item_characteristics(item) - if elaboration is None: - elaboration = descr - descr = get_item_gui_description(mlist, category, subcat, - varname, descr, elaboration, detailsp) - val = get_item_gui_value(mlist, category, kind, varname, params, extra) - table.AddRow([descr, val]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - - -def get_item_characteristics(record): - # Break out the components of an item description from its description - # record: - # - # 0 -- option-var name - # 1 -- type - # 2 -- entry size - # 3 -- ?dependancies? - # 4 -- Brief description - # 5 -- Optional description elaboration - if len(record) == 5: - elaboration = None - varname, kind, params, dependancies, descr = record - elif len(record) == 6: - varname, kind, params, dependancies, descr, elaboration = record - else: - raise ValueError, _('Badly formed options entry: %(record)s') - return varname, kind, params, dependancies, descr, elaboration - - - -def get_item_gui_value(mlist, category, kind, varname, params, extra): - """Return a representation of an item's settings.""" - # Give the category a chance to return the value for the variable - value = None - label, gui = mlist.GetConfigCategories()[category] - if hasattr(gui, 'getValue'): - value = gui.getValue(mlist, kind, varname, params) - # Filter out None, and volatile attributes - if value is None and not varname.startswith('_'): - value = getattr(mlist, varname) - # Now create the widget for this value - if kind == mm_cfg.Radio or kind == mm_cfg.Toggle: - # If we are returning the option for subscribe policy and this site - # doesn't allow open subscribes, then we have to alter the value of - # mlist.subscribe_policy as passed to RadioButtonArray in order to - # compensate for the fact that there is one fewer option. - # Correspondingly, we alter the value back in the change options - # function -scott - # - # TBD: this is an ugly ugly hack. - if varname.startswith('_'): - checked = 0 - else: - checked = value - if varname == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE: - checked = checked - 1 - # For Radio buttons, we're going to interpret the extra stuff as a - # horizontal/vertical flag. For backwards compatibility, the value 0 - # means horizontal, so we use "not extra" to get the parity right. - return RadioButtonArray(varname, params, checked, not extra) - elif (kind == mm_cfg.String or kind == mm_cfg.Email or - kind == mm_cfg.Host or kind == mm_cfg.Number): - return TextBox(varname, value, params) - elif kind == mm_cfg.Text: - if params: - r, c = params - else: - r, c = None, None - return TextArea(varname, value or '', r, c) - elif kind in (mm_cfg.EmailList, mm_cfg.EmailListEx): - if params: - r, c = params - else: - r, c = None, None - res = NL.join(value) - return TextArea(varname, res, r, c) - elif kind == mm_cfg.FileUpload: - # like a text area, but also with uploading - if params: - r, c = params - else: - r, c = None, None - container = Container() - container.AddItem('' + _('Enter the text below') + ':
      ') - container.AddItem(TextArea(varname, value or '', r, c)) - container.AddItem('
      ' + _('... or specify a file to upload') + ':
      ') - container.AddItem(FileUpload(varname+'_upload', r, c)) - return container - elif kind == mm_cfg.Select: - if params: - values, legend, selected = params - else: - values = mlist.GetAvailableLanguages() - legend = map(_, map(Utils.GetLanguageDescr, values)) - selected = values.index(mlist.preferred_language) - return SelectOptions(varname, values, legend, selected) - elif kind == mm_cfg.Topics: - # A complex and specialized widget type that allows for setting of a - # topic name, a mark button, a regexp text box, an "add after mark", - # and a delete button. Yeesh! params are ignored. - table = Table() - # This adds the html for the entry widget - def makebox(i, name, pattern, desc, empty=False, table=table): - deltag = 'topic_delete_%02d' % i - boxtag = 'topic_box_%02d' % i - reboxtag = 'topic_rebox_%02d' % i - desctag = 'topic_desc_%02d' % i - wheretag = 'topic_where_%02d' % i - addtag = 'topic_add_%02d' % i - newtag = 'topic_new_%02d' % i - if empty: - table.AddRow([Paragraph(_('Topic %(i)d')).Format(), - Paragraph(Hidden(newtag)).Format()]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="header strong center"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="header"') - else: - table.AddRow([Paragraph(_('Topic %(i)d')).Format(), - Paragraph(SubmitButton(deltag, _('Delete'))).Format()]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="header strong center"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="header"') - - table.AddRow([_('Topic name:'), - TextBox(boxtag, value=name, size=30)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - - table.AddRow([_('Regexp:'), - TextArea(reboxtag, text=pattern, - rows=4, cols=30)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - - table.AddRow([_('Description:'), - TextArea(desctag, text=desc, - rows=4, cols=30)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - - if not empty: - table.AddRow([SubmitButton(addtag, _('Add new item...')), - SelectOptions(wheretag, ('before', 'after'), - (_('...before this one.'), - _('...after this one.')), - selected=1), - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - # Now for each element in the existing data, create a widget - i = 1 - data = getattr(mlist, varname) - for name, pattern, desc, empty in data: - makebox(i, name, pattern, desc, empty) - i += 1 - # Add one more non-deleteable widget as the first blank entry, but - # only if there are no real entries. - if i == 1: - makebox(i, '', '', '', empty=True) - return table - elif kind == mm_cfg.HeaderFilter: - # A complex and specialized widget type that allows for setting of a - # spam filter rule including, a mark button, a regexp text box, an - # "add after mark", up and down buttons, and a delete button. Yeesh! - # params are ignored. - table = Table(border=0) - # This adds the html for the entry widget - def makebox(i, pattern, action, empty=False, table=table): - deltag = 'hdrfilter_delete_%02d' % i - reboxtag = 'hdrfilter_rebox_%02d' % i - actiontag = 'hdrfilter_action_%02d' % i - wheretag = 'hdrfilter_where_%02d' % i - addtag = 'hdrfilter_add_%02d' % i - newtag = 'hdrfilter_new_%02d' % i - uptag = 'hdrfilter_up_%02d' % i - downtag = 'hdrfilter_down_%02d' % i - if empty: - table.AddRow([Paragraph(_('Spam Filter Rule %(i)d')).Format(), - Paragraph(Hidden(newtag)).Format()]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="header strong center"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="header"') - else: - table.AddRow([Paragraph(_('Spam Filter Rule %(i)d')).Format(), - Paragraph(SubmitButton(deltag, _('Delete'))).Format()]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="header strong center"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="header"') - - table.AddRow([_('Spam Filter Regexp:'), - TextArea(reboxtag, text=pattern, - rows=4, cols=30)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - - values = [mm_cfg.DEFER, mm_cfg.HOLD, mm_cfg.REJECT, - mm_cfg.DISCARD, mm_cfg.ACCEPT] - try: - checked = values.index(action) - except ValueError: - checked = 0 - radio = RadioButtonArray( - actiontag, - (_('Defer'), _('Hold'), _('Reject'), - _('Discard'), _('Accept')), - values=values, - checked=checked).Format() - table.AddRow([_('Action:'), radio]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center"') - if not empty: - table.AddRow([SubmitButton(addtag, _('Add new item...')), - SelectOptions(wheretag, ('before', 'after'), - (_('...before this one.'), - _('...after this one.')), - selected=1) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - # BAW: IWBNI we could disable the up and down buttons for the - # first and last item respectively, but it's not easy to know - # which is the last item, so let's not worry about that for - # now. - table.AddRow([SubmitButton(uptag, _('Move rule up')), - SubmitButton(downtag, _('Move rule down'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - # Now for each element in the existing data, create a widget - i = 1 - data = getattr(mlist, varname) - for pattern, action, empty in data: - makebox(i, pattern, action, empty) - i += 1 - # Add one more non-deleteable widget as the first blank entry, but - # only if there are no real entries. - if i == 1: - makebox(i, '', mm_cfg.DEFER, empty=True) - return table - elif kind == mm_cfg.Checkbox: - return CheckBoxArray(varname, *params) - else: - assert 0, 'Bad gui widget type: %s' % kind - - - -def get_item_gui_description(mlist, category, subcat, - varname, descr, elaboration, detailsp): - # Return the item's description, with link to details. - # - # Details are not included if this is a VARHELP page, because that /is/ - # the details page! - if detailsp: - if subcat: - varhelp = '/?VARHELP=%s/%s/%s' % (category, subcat, varname) - else: - varhelp = '/?VARHELP=%s/%s' % (category, varname) - if descr == elaboration: - linktext = _('(Edit %(varname)s)') - else: - linktext = _('(Details for %(varname)s)') - link = Link(mlist.GetScriptURL('admin') + varhelp, - linktext).Format() - text = '%s
      %s' % (descr, link) - else: - text = descr - if varname[0] == '_': - text += Paragraph(_('''Note: setting this value performs an immediate action but does not modify permanent state.''')).Format() - return text - - - -def membership_options(mlist, subcat, cgidata, doc, form): - # Show the main stuff - adminurl = mlist.GetScriptURL('admin', absolute=1) - container = Container() - # If we're in the list subcategory, show the membership list - if subcat == 'add': - container.AddItem(Header(2, _('Mass Subscriptions')).Format()) - mass_subscribe(mlist, container) - return container - if subcat == 'remove': - container.AddItem(Header(2, _('Mass Removals')).Format()) - mass_remove(mlist, container) - return container - # Otherwise... - container.AddItem(Header(2, _('Membership List')).Format()) - # Add a "search for member" button - link = Link('http://www.python.org/doc/current/lib/re-syntax.html', - _('(help)')).Format() - container.AddItem(Paragraph( - _('Find member %(link)s:') + - TextBox('findmember', value=cgidata.getvalue('findmember', '')).Format() + - SubmitButton('findmember_btn', _('Search...')).Format()).Format()) - - usertable = Table(css='class="mm_usertable"') - # If there are more members than allowed by chunksize, then we split the - # membership up alphabetically. Otherwise just display them all. - chunksz = mlist.admin_member_chunksize - # The email addresses had /better/ be ASCII, but might be encoded in the - # database as Unicodes. - all = [_m.encode() for _m in mlist.getMembers()] - all.sort(lambda x, y: cmp(x.lower(), y.lower())) - # See if the query has a regular expression - regexp = cgidata.getvalue('findmember', '').strip() - if regexp: - try: - cre = re.compile(regexp, re.IGNORECASE) - except re.error: - doc.addError(_('Bad regular expression: ') + regexp) - else: - # BAW: There's got to be a more efficient way of doing this! - names = [mlist.getMemberName(s) or '' for s in all] - all = [a for n, a in zip(names, all) - if cre.search(n) or cre.search(a)] - chunkindex = None - bucket = None - actionurl = None - if len(all) < chunksz: - members = all - else: - # Split them up alphabetically, and then split the alphabetical - # listing by chunks - buckets = {} - for addr in all: - members = buckets.setdefault(addr[0].lower(), []) - members.append(addr) - # Now figure out which bucket we want - bucket = None - qs = {} - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qsenviron = os.environ.get('QUERY_STRING') - if qsenviron: - qs = cgi.parse_qs(qsenviron) - bucket = qs.get('letter', 'a')[0].lower() - if bucket not in digits + lowercase: - bucket = None - if not bucket or not buckets.has_key(bucket): - keys = buckets.keys() - keys.sort() - bucket = keys[0] - members = buckets[bucket] - action = adminurl + '/members?letter=%s' % bucket - if len(members) <= chunksz: - form.set_action(action) - else: - i, r = divmod(len(members), chunksz) - numchunks = i + (not not r * 1) - # Now chunk them up - chunkindex = 0 - if qs.has_key('chunk'): - try: - chunkindex = int(qs['chunk'][0]) - except ValueError: - chunkindex = 0 - if chunkindex < 0 or chunkindex > numchunks: - chunkindex = 0 - members = members[chunkindex*chunksz:(chunkindex+1)*chunksz] - # And set the action URL - form.set_action(action + '&chunk=%s' % chunkindex) - - # So now members holds all the addresses we're going to display - allcnt = len(all) - if bucket: - membercnt = len(members) - usertable.AddRow([Header(3, - _('%(allcnt)s members total, %(membercnt)s shown')).Format()]) - else: - usertable.AddRow([Header(3, - _('%(allcnt)s members total')).Format()]) - usertable.AddCellInfo(usertable.GetCurrentRowIndex(), - usertable.GetCurrentCellIndex(), - colspan=OPTCOLUMNS, - css='class="center"') - - # Add the alphabetical links - if bucket: - cells = [] - for letter in digits + lowercase: - if not buckets.get(letter): - continue - url = adminurl + '/members?letter=%s' % letter - if letter == bucket: - show = Bold('[%s]' % letter.upper()).Format() - else: - show = letter.upper() - cells.append(Link(url, show).Format()) - joiner = ' '*2 + '\n' - usertable.AddRow([joiner.join(cells)]) - usertable.AddCellInfo(usertable.GetCurrentRowIndex(), - usertable.GetCurrentCellIndex(), - colspan=OPTCOLUMNS, - css='class="center"') - - usertable.AddRow([(h) for h in (_('unsub'), - _('member address
      member name'), - _('mod'), _('hide'), - _('nomail
      [reason]'), - _('ack'), _('not metoo'), - _('nodupes'), - _('digest'), _('plain'), - _('language'))]) - rowindex = usertable.GetCurrentRowIndex() - for i in range(OPTCOLUMNS): - usertable.AddCellInfo(rowindex, i, css='class="header strong center"') - - # Find the longest name in the list - longest = 0 - if members: - names = filter(None, [mlist.getMemberName(s) for s in members]) - # Make the name field at least as long as the longest email address - longest = max([len(s) for s in names + members]) - - # Abbreviations for delivery status details - ds_abbrevs = {MemberAdaptor.UNKNOWN : _('?'), - MemberAdaptor.BYUSER : _('U'), - MemberAdaptor.BYADMIN : _('A'), - MemberAdaptor.BYBOUNCE: _('B'), - } - - # Now populate the rows - highlight = 1 - for addr in members: - link = Link(mlist.GetOptionsURL(addr, obscure=1), - mlist.getMemberCPAddress(addr)) - fullname = Utils.uncanonstr(mlist.getMemberName(addr), - mlist.preferred_language) - name = TextBox(addr + '_realname', fullname, size=longest).Format() - cells = [CheckBox(addr + '_unsub', 'off', 0).Format(), - link.Format() + '
      ' + - name + - Hidden('user', urllib.quote(addr)).Format(), - ] - # Do the `mod' option - if mlist.getMemberOption(addr, mm_cfg.Moderate): - value = 'on' - checked = 1 - else: - value = 'off' - checked = 0 - box = CheckBox('%s_mod' % addr, value, checked) - cells.append(box.Format()) - for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes'): - extra = '' - if opt == 'nomail': - status = mlist.getDeliveryStatus(addr) - if status == MemberAdaptor.ENABLED: - value = 'off' - checked = 0 - else: - value = 'on' - checked = 1 - extra = '[%s]' % ds_abbrevs[status] - elif mlist.getMemberOption(addr, mm_cfg.OPTINFO[opt]): - value = 'on' - checked = 1 - else: - value = 'off' - checked = 0 - box = CheckBox('%s_%s' % (addr, opt), value, checked) - cells.append(box.Format() + extra) - - # This code is less efficient than the original which did a has_key on - # the underlying dictionary attribute. This version is slower and - # less memory efficient. It points to a new MemberAdaptor interface - # method. - if addr in mlist.getRegularMemberKeys(): - cells.append(CheckBox(addr + '_digest', 'off', 0).Format()) - else: - cells.append(CheckBox(addr + '_digest', 'on', 1).Format()) - if mlist.getMemberOption(addr, mm_cfg.OPTINFO['plain']): - value = 'on' - checked = 1 - else: - value = 'off' - checked = 0 - cells.append(CheckBox('%s_plain' % addr, value, checked)) - - # User's preferred language - langpref = mlist.getMemberLanguage(addr) - langs = mlist.GetAvailableLanguages() - langdescs = [_(Utils.GetLanguageDescr(lang)) for lang in langs] - try: - selected = langs.index(langpref) - except ValueError: - selected = 0 - cells.append(SelectOptions(addr + '_language', langs, - langdescs, selected).Format()) - - # Put each cell into the row and give format to that row - usertable.AddRow(cells) - if highlight: - usertable.AddRowInfo(usertable.GetCurrentRowIndex(), css='class="title center"') - highlight = not highlight - else: - highlight = 1 - usertable.AddRowInfo(usertable.GetCurrentRowIndex(), css='class="center"') - - # Add the usertable and a legend - legend = UnorderedList() - legend.AddItem( - _('unsub -- Click on this to unsubscribe the member.')) - legend.AddItem( - _("""mod -- The user's personal moderation flag. If this is - set, postings from them will be moderated, otherwise they will be - approved.""")) - legend.AddItem( - _("""hide -- Is the member's address concealed on - the list of subscribers?""")) - legend.AddItem( - _('nomail -- Is delivery to the member disabled? If so, an abbreviation will be given describing the reason for the disabled delivery:') + '\n' + - '
        ' + '\n' + - _('''
      • U -- Delivery was disabled by the user via their personal options page.
      • ''') + '\n' + - _('''
      • A -- Delivery was disabled by the list administrators.
      • ''') + '\n' + - _('''
      • B -- Delivery was disabled by the system due to excessive bouncing from the member's address.
      • ''') + '\n' + - _('''
      • ? -- The reason for disabled delivery isn't known. This is the case for all memberships which were disabled in older versions of Mailman.
      • ''') + '\n' + - '
      ' + '\n') - legend.AddItem( - _('''ack -- Does the member get acknowledgements of their - posts?''')) - legend.AddItem( - _('''not metoo -- Does the member want to avoid copies of their - own postings?''')) - legend.AddItem( - _('''nodupes -- Does the member want to avoid duplicates of the - same message?''')) - legend.AddItem( - _('''digest -- Does the member get messages in digests? - (otherwise, individual messages)''')) - legend.AddItem( - _('''plain -- If getting digests, does the member get plain - text digests? (otherwise, MIME)''')) - legend.AddItem(_("language -- Language preferred by the user")) - addlegend = '' - parsedqs = 0 - qsenviron = os.environ.get('QUERY_STRING') - if qsenviron: - qs = cgi.parse_qs(qsenviron).get('legend') - if qs and isinstance(qs, ListType): - qs = qs[0] - if qs == 'yes': - addlegend = 'legend=yes&' - if addlegend: - container.AddItem(legend.Format()) - container.AddItem(Paragraph( - Link(adminurl + '/members/list', - _('Click here to hide the legend for this table.')))) - else: - container.AddItem(Paragraph( - Link(adminurl + '/members/list?legend=yes', - _('Click here to include the legend for this table.')))) - - container.AddItem(usertable) - - # There may be additional chunks - if chunkindex is not None: - buttons = [] - url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) - footer = _('''To view more members, click on the appropriate range listed below:''') - chunkmembers = buckets[bucket] - last = len(chunkmembers) - for i in range(numchunks): - if i == chunkindex: - continue - start = chunkmembers[i*chunksz] - end = chunkmembers[min((i+1)*chunksz, last)-1] - link = Link(url + 'chunk=%d' % i, _('from %(start)s to %(end)s')) - buttons.append(link) - buttons = UnorderedList(*buttons) - container.AddItem(Paragraph(footer + buttons.Format()).Format()) - return container - - - -def mass_subscribe(mlist, container): - # MASS SUBSCRIBE - table = Table() - table.AddRow([ - _('Subscribe these users now or invite them?'), - RadioButtonArray('subscribe_or_invite', - (_('Subscribe'), _('Invite')), - 0, values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow([ - _('Send welcome messages to new subscribees?'), - RadioButtonArray('send_welcome_msg_to_this_batch', - (_('No'), _('Yes')), - mlist.send_welcome_msg, - values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow([ - _('Send notifications of new subscriptions to the list owner?'), - RadioButtonArray('send_notifications_to_list_owner', - (_('No'), _('Yes')), - mlist.admin_notify_mchanges, - values=(0,1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow([ - _('Enter one address per line:'), - TextArea(name='subscribees', - rows=10, cols='70%').Format() + - Paragraph(_('... or specify a file to upload:') + - FileUpload('subscribees_upload', cols='50').Format()).Format() - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - # Invitation text - table.AddRow([_("""Enter additional text to be added to the - top of your invitation or the subscription notification. Include at least - one blank line at the end:"""), - TextArea(name='invitation', rows=10, cols='70%')]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - container.AddItem(table) - - - -def mass_remove(mlist, container): - # MASS UNSUBSCRIBE - table = Table() - table.AddRow([ - _('Send unsubscription acknowledgement to the user?'), - RadioButtonArray('send_unsub_ack_to_this_batch', - (_('No'), _('Yes')), - 0, values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow([ - _('Send notifications to the list owner?'), - RadioButtonArray('send_unsub_notifications_to_list_owner', - (_('No'), _('Yes')), - mlist.admin_notify_mchanges, - values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow([_('Enter one address per line:'), - TextArea(name='unsubscribees', - rows=10, cols='70%').Format() - + Paragraph(_('... or specify a file to upload:') - + FileUpload('unsubscribees_upload', cols='50').Format()).Format() - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - container.AddItem(table) - - - -def password_inputs(mlist): - adminurl = mlist.GetScriptURL('admin', absolute=1) - container = Container() - container.AddItem(Header(2, _('Change list ownership passwords')).Format()) - container.AddItem(Paragraph(_('''The list administrators are the people who have ultimate control over all parameters of this mailing list. They are able to change any list configuration variable available through these administration web pages.'''))) - container.AddItem(Paragraph(_('''The list moderators have more limited permissions; they are not able to change any list configuration variable, but they are allowed to tend to pending administration requests, including approving or rejecting held subscription requests, and disposing of held postings. Of course, the list administrators can also tend to pending requests.'''))) - container.AddItem(Paragraph(_('''In order to split the list ownership duties into administrators and moderators, you must set a separate moderator password in the fields below, and also provide the email addresses of the list moderators in the general options section.'''))) - # Set up the admin password table on the left - atable = Table() - atable.AddRow([_('Enter new administrator password:'), - PasswordBox('newpw', size=20)]) - atable.AddCellInfo(atable.GetCurrentRowIndex(), 0, css='class="description"') - atable.AddCellInfo(atable.GetCurrentRowIndex(), 1, css='class="value"') - atable.AddRow([_('Confirm administrator password:'), - PasswordBox('confirmpw', size=20)]) - atable.AddCellInfo(atable.GetCurrentRowIndex(), 0, css='class="description"') - atable.AddCellInfo(atable.GetCurrentRowIndex(), 1, css='class="value"') - # Set up the moderator password table on the right - mtable = Table() - mtable.AddRow([_('Enter new moderator password:'), - PasswordBox('newmodpw', size=20)]) - mtable.AddCellInfo(mtable.GetCurrentRowIndex(), 0, css='class="description"') - mtable.AddCellInfo(mtable.GetCurrentRowIndex(), 1, css='class="value"') - mtable.AddRow([_('Confirm moderator password:'), - PasswordBox('confirmmodpw', size=20)]) - mtable.AddCellInfo(mtable.GetCurrentRowIndex(), 0, css='class="description"') - mtable.AddCellInfo(mtable.GetCurrentRowIndex(), 1, css='class="value"') - - # Add these tables to the overall password container - container.AddItem(atable) - container.AddItem(mtable) - return container - - - -def submit_button(name='submit'): - container = Container() - container.AddItem(Paragraph(SubmitButton(name, _('Submit Your Changes'))).Format(css='class="mm_submit"')) - return container - - - -def change_options(mlist, category, subcat, cgidata, doc): - def safeint(formvar, defaultval=None): - try: - return int(cgidata.getvalue(formvar)) - except (ValueError, TypeError): - return defaultval - confirmed = 0 - # Handle changes to the list moderator password. Do this before checking - # the new admin password, since the latter will force a reauthentication. - new = cgidata.getvalue('newmodpw', '').strip() - confirm = cgidata.getvalue('confirmmodpw', '').strip() - if new or confirm: - if new == confirm: - mlist.mod_password = sha.new(new).hexdigest() - # No re-authentication necessary because the moderator's - # password doesn't get you into these pages. - else: - doc.addMessage(_('Moderator passwords did not match'), css='class="message error strong"') - # Handle changes to the list administrator password - new = cgidata.getvalue('newpw', '').strip() - confirm = cgidata.getvalue('confirmpw', '').strip() - if new or confirm: - if new == confirm: - mlist.password = sha.new(new).hexdigest() - # Set new cookie - print mlist.MakeCookie(mm_cfg.AuthListAdmin) - else: - doc.addMessage(_('Administrator passwords did not match'), css='class="message error strong"') - # Give the individual gui item a chance to process the form data - categories = mlist.GetConfigCategories() - label, gui = categories[category] - # BAW: We handle the membership page special... for now. - if category <> 'members': - gui.handleForm(mlist, category, subcat, cgidata, doc) - # mass subscription, removal processing for members category - subscribers = '' - subscribers += cgidata.getvalue('subscribees', '') - subscribers += cgidata.getvalue('subscribees_upload', '') - if subscribers: - entries = filter(None, [n.strip() for n in subscribers.splitlines()]) - send_welcome_msg = safeint('send_welcome_msg_to_this_batch', - mlist.send_welcome_msg) - send_admin_notif = safeint('send_notifications_to_list_owner', - mlist.admin_notify_mchanges) - # Default is to subscribe - subscribe_or_invite = safeint('subscribe_or_invite', 0) - invitation = cgidata.getvalue('invitation', '') - digest = mlist.digest_is_default - if not mlist.digestable: - digest = 0 - if not mlist.nondigestable: - digest = 1 - subscribe_errors = [] - subscribe_success = [] - # Now cruise through all the subscribees and do the deed. BAW: we - # should limit the number of "Successfully subscribed" status messages - # we display. Try uploading a file with 10k names -- it takes a while - # to render the status page. - for entry in entries: - safeentry = Utils.websafe(entry) - fullname, address = parseaddr(entry) - # Canonicalize the full name - fullname = Utils.canonstr(fullname, mlist.preferred_language) - userdesc = UserDesc(address, fullname, - Utils.MakeRandomPassword(), - digest, mlist.preferred_language) - try: - if subscribe_or_invite: - if mlist.isMember(address): - raise Errors.MMAlreadyAMember - else: - mlist.InviteNewMember(userdesc, invitation) - else: - mlist.ApprovedAddMember(userdesc, send_welcome_msg, - send_admin_notif, invitation, - whence='admin mass sub') - except Errors.MMAlreadyAMember: - subscribe_errors.append((safeentry, _('Already a member'))) - except Errors.MMBadEmailError: - if userdesc.address == '': - subscribe_errors.append((_('<blank line>'), - _('Bad/Invalid email address'))) - else: - subscribe_errors.append((safeentry, - _('Bad/Invalid email address'))) - except Errors.MMHostileAddress: - subscribe_errors.append( - (safeentry, _('Hostile address (illegal characters)'))) - except Errors.MembershipIsBanned, pattern: - subscribe_errors.append( - (safeentry, _('Banned address (matched %(pattern)s)'))) - else: - member = Utils.uncanonstr(formataddr((fullname, address))) - subscribe_success.append(Utils.websafe(member)) - - if subscribe_success: - if subscribe_or_invite: - msg = _('Successfully invited:') - else: - msg = _('Successfully subscribed:') - doc.AddItem(Div(Header(3, msg).Format() - + UnorderedList(*subscribe_success).Format(css='class="mailaddresses"') - ).Format(css='class="message success"')) - - if subscribe_errors: - if subscribe_or_invite: - msg = _('Error inviting:') - else: - msg = _('Error subscribing:') - items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] - doc.AddItem(Div(Header(3, msg).Format() - + UnorderedList(*items).Format(css='class="mailaddresses"') - ).Format(css='class="message error"')) - - # Unsubscriptions - removals = '' - if cgidata.has_key('unsubscribees'): - removals += cgidata['unsubscribees'].value - if cgidata.has_key('unsubscribees_upload') and \ - cgidata['unsubscribees_upload'].value: - removals += cgidata['unsubscribees_upload'].value - if removals: - names = filter(None, [n.strip() for n in removals.splitlines()]) - send_unsub_notifications = int( - cgidata['send_unsub_notifications_to_list_owner'].value) - userack = int( - cgidata['send_unsub_ack_to_this_batch'].value) - unsubscribe_errors = [] - unsubscribe_success = [] - for addr in names: - try: - mlist.ApprovedDeleteMember( - addr, whence='admin mass unsub', - admin_notif=send_unsub_notifications, - userack=userack) - unsubscribe_success.append(Utils.websafe(addr)) - except Errors.NotAMemberError: - unsubscribe_errors.append(Utils.websafe(addr)) - - if unsubscribe_success: - msg = _('Successfully Unsubscribed:') - doc.AddItem(Div(Header(3, msg).Format() - + UnorderedList(*unsubscribe_success).Format(css='class="mailaddresses"') - ).Format(css='class="message success"')) - - if unsubscribe_errors: - msg = _('Cannot unsubscribe non-members:') - doc.AddItem(Div(Header(3, msg).Format() - + UnorderedList(*unsubscribe_errors).Format(css='class="mailaddresses"') - ).Format(css='class="message error"')) - - # See if this was a moderation bit operation - if cgidata.has_key('allmodbit_btn'): - val = cgidata.getvalue('allmodbit_val') - try: - val = int(val) - except VallueError: - val = None - if val not in (0, 1): - doc.addError(_('Bad moderation flag value')) - else: - for member in mlist.getMembers(): - mlist.setMemberOption(member, mm_cfg.Moderate, val) - # do the user options for members category - if cgidata.has_key('setmemberopts_btn') and cgidata.has_key('user'): - user = cgidata['user'] - if type(user) is ListType: - users = [] - for ui in range(len(user)): - users.append(urllib.unquote(user[ui].value)) - else: - users = [urllib.unquote(user.value)] - errors = [] - removes = [] - for user in users: - if cgidata.has_key('%s_unsub' % user): - try: - mlist.ApprovedDeleteMember(user, whence='member mgt page') - removes.append(user) - except Errors.NotAMemberError: - errors.append((user, _('Not subscribed'))) - continue - if not mlist.isMember(user): - doc.addMessage(_('Ignoring changes to deleted member: %(user)s'), - tag=_('Warning: '), - css='class="message warning"') - continue - value = cgidata.has_key('%s_digest' % user) - try: - mlist.setMemberOption(user, mm_cfg.Digests, value) - except (Errors.AlreadyReceivingDigests, - Errors.AlreadyReceivingRegularDeliveries, - Errors.CantDigestError, - Errors.MustDigestError): - # BAW: Hmm... - pass - - newname = cgidata.getvalue(user+'_realname', '') - newname = Utils.canonstr(newname, mlist.preferred_language) - mlist.setMemberName(user, newname) - - newlang = cgidata.getvalue(user+'_language') - oldlang = mlist.getMemberLanguage(user) - if Utils.IsLanguage(newlang) and newlang <> oldlang: - mlist.setMemberLanguage(user, newlang) - - moderate = not not cgidata.getvalue(user+'_mod') - mlist.setMemberOption(user, mm_cfg.Moderate, moderate) - - # Set the `nomail' flag, but only if the user isn't already - # disabled (otherwise we might change BYUSER into BYADMIN). - if cgidata.has_key('%s_nomail' % user): - if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED: - mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) - else: - mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) - for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): - opt_code = mm_cfg.OPTINFO[opt] - if cgidata.has_key('%s_%s' % (user, opt)): - mlist.setMemberOption(user, opt_code, 1) - else: - mlist.setMemberOption(user, opt_code, 0) - # Give some feedback on who's been removed - if removes: - msg = _('Successfully Removed:') - doc.AddItem(Div(Header(3, msg).Format() - + UnorderedList(*removes).Format(css='class="mailaddresses"') - ).Format(css='class="message success"')) - if errors: - msg = _('Error Unsubscribing:') - items = ['%s -- %s' % (x[0], x[1]) for x in errors] - doc.AddItem(Div(Header(3, msg).Format() - + UnorderedList(*items).Format(css='class="mailaddresses"') - ).Format(css='class="message error"')) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/admindb.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/admindb.py deleted file mode 100755 index 3355014..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/admindb.py +++ /dev/null @@ -1,833 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Produce and process the pending-approval items for a list.""" - -import sys -import os -import cgi -import errno -import signal -import email -import time -from types import ListType -from urllib import quote_plus, unquote_plus - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MailList -from Mailman import Errors -from Mailman import Message -from Mailman import i18n -from Mailman.Handlers.Moderate import ModeratedMemberPost -from Mailman.ListAdmin import readMessage -from Mailman.Cgi import Auth -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -EMPTYSTRING = '' -NL = '\n' - -# Set up i18n. Until we know which list is being requested, we use the -# server's default. -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - -EXCERPT_HEIGHT = 10 -EXCERPT_WIDTH = 76 - - - -def helds_by_sender(mlist): - heldmsgs = mlist.GetHeldMessageIds() - bysender = {} - for id in heldmsgs: - sender = mlist.GetRecord(id)[1] - bysender.setdefault(sender, []).append(id) - return bysender - - -def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): - # We can't use a RadioButtonArray here because horizontal placement can be - # confusing to the user and vertical placement takes up too much - # real-estate. This is a hack! - btns = Table(css='class="radiobuttons center"') - btns.AddRow([text for text in labels]) - btns.AddRow([RadioButton(btnname, value, default) - for value, default in zip(values, defaults)]) - return btns - - - -def main(): - # Figure out which list is being requested - parts = Utils.GetPathPieces() - if not parts: - handle_no_list() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - handle_no_list(_('No such list %(safelistname)s. ')) - syslog('error', 'No such list "%s": %s\n', listname, e) - return - - # Now that we know which list to use, set the system's language to it. - i18n.set_language(mlist.preferred_language) - - # Make sure the user is authorized to see this page. - cgidata = cgi.FieldStorage(keep_blank_values=1) - - if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, - mm_cfg.AuthListModerator, - mm_cfg.AuthSiteAdmin), - cgidata.getvalue('adminpw', '')): - if cgidata.has_key('adminpw'): - # This is a re-authorization attempt - msg = _('Authorization failed.') - else: - msg = '' - msg = Paragraph(msg).Format(css='class="strong"') - Auth.loginpage(mlist, 'admindb', msg=msg) - return - - # Set up the results document - doc = Document() - doc.set_language(mlist.preferred_language) - - # See if we're requesting all the messages for a particular sender, or if - # we want a specific held message. - sender = None - msgid = None - details = None - envar = os.environ.get('QUERY_STRING') - if envar: - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = cgi.parse_qs(envar).get('sender') - if qs and type(qs) == ListType: - sender = qs[0] - qs = cgi.parse_qs(envar).get('msgid') - if qs and type(qs) == ListType: - msgid = qs[0] - qs = cgi.parse_qs(envar).get('details') - if qs and type(qs) == ListType: - details = qs[0] - - # We need a signal handler to catch the SIGTERM that can come from Apache - # when the user hits the browser's STOP button. See the comment in - # admin.py for details. - # - # BAW: Strictly speaking, the list should not need to be locked just to - # read the request database. However the request database asserts that - # the list is locked in order to load it and it's not worth complicating - # that logic. - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - mlist.Unlock() - # ...and ensure we exit, otherwise race conditions could cause us to - # enter MailList.Save() while we're in the unlocked state, and that - # could be bad! - sys.exit(0) - - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) - - realname = mlist.real_name - if not cgidata.keys(): - # If this is not a form submission (i.e. there are no keys in the - # form), then we don't need to do much special. - doc.SetTitle(_('%(realname)s Administrative Database')) - elif not details: - # This is a form submission - doc.SetTitle(_('%(realname)s Administrative Database Results')) - process_form(mlist, doc, cgidata) - # Now print the results and we're done. Short circuit for when there - # are no pending requests, but be sure to save the results! - if not mlist.NumRequestsPending(): - title = _('%(realname)s Administrative Database') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph( - _('There are no pending requests.') + - Link(mlist.GetScriptURL('admindb', absolute=1), - _('Click here to reload this page.')).Format() - )) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - mlist.Save() - return - - admindburl = mlist.GetScriptURL('admindb', absolute=1) - form = Form(admindburl) - # Add the instructions template - if details == 'instructions': - title = _('Detailed instructions for the administrative database') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - else: - title = _('Pending moderator requests') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - if details <> 'instructions': - form.AddItem(Paragraph( - SubmitButton('submit', _('Submit All Data'))).Format(css='class="mm_submit tied"')) - if not (sender or msgid): - form.AddItem(Paragraph( - CheckBox('discardalldefersp', 0).Format() + - _('Discard all messages marked Defer') + '.' - ).Format(css='class="mm_submit tied"')) - # Add a link back to the overview, if we're not viewing the overview! - adminurl = mlist.GetScriptURL('admin', absolute=1) - d = {'listname' : mlist.real_name, - 'detailsurl': admindburl + '?details=instructions', - 'summaryurl': admindburl, - 'viewallurl': admindburl + '?details=all', - 'adminurl' : adminurl, - 'filterurl' : adminurl + '/privacy/sender', - } - addform = 1 - if sender: - esender = Utils.websafe(sender) - d['description'] = _("all of %(esender)s's held messages.") - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_sender_requests(mlist, form, sender) - elif msgid: - d['description'] = _('a single held message.') - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_message_requests(mlist, form, msgid) - elif details == 'all': - d['description'] = _('all held messages.') - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_detailed_requests(mlist, form) - elif details == 'instructions': - doc.AddItem(Utils.maketext('admindbdetails.html', d, - raw=1, mlist=mlist)) - addform = 0 - else: - # Show a summary of all requests - doc.AddItem(Utils.maketext('admindbsummary.html', d, - raw=1, mlist=mlist)) - num = show_pending_subs(mlist, form) - num += show_pending_unsubs(mlist, form) - num += show_helds_overview(mlist, form) - addform = num > 0 - # Finish up the document, adding buttons to the form - if addform: - doc.AddItem(form) - if not (sender or msgid): - form.AddItem(Paragraph( - CheckBox('discardalldefersp', 0).Format() + - _('Discard all messages marked Defer') + '.' - ).Format(css='class="mm_submit tied"')) - form.AddItem(Paragraph(SubmitButton('submit', - _('Submit All Data')) - ).Format(css='class="mm_submit tied"')) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - # Commit all changes - mlist.Save() - finally: - mlist.Unlock() - - - -def handle_no_list(msg=''): - # Print something useful if no list was given. - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - title = _('Mailman Administrative Database Error') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(msg) - url = Utils.ScriptURL('admin', absolute=1) - link = Link(url, _('list of available mailing lists.')).Format() - doc.AddItem(_('You must specify a list name. Here is the %(link)s')) - doc.AddItem(MailmanLogo()) - print doc.Format() - - - -def show_pending_subs(mlist, form): - # Add the subscription request section - pendingsubs = mlist.GetSubscriptionIds() - if not pendingsubs: - return 0 - form.AddItem(Header(2, _('Subscription Requests'))) - table = Table(css='class="pending_subs"') - table.AddRow([_('Address/name'), - _('Your decision'), - _('Reason for refusal') - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="center strong header"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center strong header"') - table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="center strong header"') - # Alphabetical order by email address - byaddrs = {} - for id in pendingsubs: - addr = mlist.GetRecord(id)[1] - byaddrs.setdefault(addr, []).append(id) - addrs = byaddrs.keys() - addrs.sort() - num = 0 - highlight = 1 - for addr, ids in byaddrs.items(): - # Eliminate duplicates - for id in ids[1:]: - mlist.HandleRequest(id, mm_cfg.DISCARD) - id = ids[0] - time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) - fullname = Utils.uncanonstr(fullname, mlist.preferred_language) - radio = RadioButtonArray(id, (_('Defer'), - _('Approve'), - _('Reject'), - _('Discard')), - values=(mm_cfg.DEFER, - mm_cfg.SUBSCRIBE, - mm_cfg.REJECT, - mm_cfg.DISCARD), - checked=0).Format() - if addr not in mlist.ban_list: - radio += '
      ' + CheckBox('ban-%d' % id, 1).Format() + \ - ' ' + _('Permanently ban from this list') - # While the address may be a unicode, it must be ascii - paddr = addr.encode('us-ascii', 'replace') - table.AddRow(['%s
      %s' % (paddr, Utils.websafe(fullname)), - radio, - TextBox('comment-%d' % id, size=40) - ]) - if highlight: - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="left title"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left title"') - table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="left title"') - else: - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="left"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="left"') - highlight = not highlight - num += 1 - if num > 0: - form.AddItem(table) - return num - - - -def show_pending_unsubs(mlist, form): - # Add the pending unsubscription request section - lang = mlist.preferred_language - pendingunsubs = mlist.GetUnsubscriptionIds() - if not pendingunsubs: - return 0 - table = Table(css='class="pending_unsubs"') - table.AddRow([_('User address/name'), - _('Your decision'), - _('Reason for refusal') - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="center header strong"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center header strong"') - table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="center header strong"') - # Alphabetical order by email address - byaddrs = {} - for id in pendingunsubs: - addr = mlist.GetRecord(id)[1] - byaddrs.setdefault(addr, []).append(id) - addrs = byaddrs.keys() - addrs.sort() - num = 0 - highlight = 1 - for addr, ids in byaddrs.items(): - # Eliminate duplicates - for id in ids[1:]: - mlist.HandleRequest(id, mm_cfg.DISCARD) - id = ids[0] - addr = mlist.GetRecord(id) - try: - fullname = Utils.uncanonstr(mlist.getMemberName(addr), lang) - except Errors.NotAMemberError: - # They must have been unsubscribed elsewhere, so we can just - # discard this record. - mlist.HandleRequest(id, mm_cfg.DISCARD) - continue - table.AddRow(['%s
      %s' % (addr, Utils.websafe(fullname)), - RadioButtonArray(id, (_('Defer'), - _('Approve'), - _('Reject'), - _('Discard')), - values=(mm_cfg.DEFER, - mm_cfg.UNSUBSCRIBE, - mm_cfg.REJECT, - mm_cfg.DISCARD), - checked=0), - TextBox('comment-%d' % id, size=45) - ]) - if highlight: - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="left title"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center title"') - table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="left title"') - else: - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="left"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center"') - table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="left"') - highlight = not highlight - num += 1 - if num > 0: - form.AddItem(Header(2, _('Unsubscription Requests'))) - form.AddItem(table) - return num - - - -def show_helds_overview(mlist, form): - # Sort the held messages by sender - bysender = helds_by_sender(mlist) - if not bysender: - return 0 - # Add the by-sender overview tables - admindburl = mlist.GetScriptURL('admindb', absolute=1) - table = Table(css='class="helds_overview"') - form.AddItem(Header(2, _('Postings Held for Approval'))) - form.AddItem(table) - senders = bysender.keys() - senders.sort() - for sender in senders: - qsender = quote_plus(sender) - esender = Utils.websafe(sender) - senderurl = admindburl + '?sender=' + qsender - # The encompassing sender table - stable = Table() - stable.AddRow([Paragraph(_('From: ') + esender)]) - stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2, css='class="header strong center"') - left = Table(css='class="helds_overview left"') - left.AddRow([_('Action to take on all these held messages:')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - btns = hacky_radio_buttons( - 'senderaction-' + qsender, - (_('Defer'), _('Accept'), _('Reject'), _('Discard')), - (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), - (1, 0, 0, 0)) - left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - left.AddRow([ - CheckBox('senderpreserve-' + qsender, 1).Format() + - _('Preserve messages for the site administrator') - ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - left.AddRow([ - CheckBox('senderforward-' + qsender, 1).Format() + - _('Forward messages (individually) to:') - ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - left.AddRow([ - TextBox('senderforwardto-' + qsender, - value=mlist.GetOwnerEmail(), size=47) - ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - # If the sender is a member and the message is being held due to a - # moderation bit, give the admin a chance to clear the member's mod - # bit. If this sender is not a member and is not already on one of - # the sender filters, then give the admin a chance to add this sender - # to one of the filters. - if mlist.isMember(sender): - if mlist.getMemberOption(sender, mm_cfg.Moderate): - left.AddRow([ - CheckBox('senderclearmodp-' + qsender, 1).Format() + - _("Clear this member's moderate flag.") - ]) - else: - left.AddRow( - [_('The sender is now a member of this list') + '.']) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - elif sender not in (mlist.accept_these_nonmembers + - mlist.hold_these_nonmembers + - mlist.reject_these_nonmembers + - mlist.discard_these_nonmembers): - left.AddRow([ - CheckBox('senderfilterp-' + qsender, 1).Format() + - _('Add %(esender)s to one of these sender filters:') - ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - btns = hacky_radio_buttons( - 'senderfilter-' + qsender, - (_('Accepts'), _('Holds'), _('Rejects'), _('Discards')), - (mm_cfg.ACCEPT, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD), - (0, 0, 0, 1)) - left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - if sender not in mlist.ban_list: - left.AddRow([ - CheckBox('senderbanp-' + qsender, 1).Format() + - _("""Ban %(esender)s from ever subscribing to this - mailing list""") + '.']) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - right = Table(css='class="helds_overview right"') - right.AddRow([ - _("""Click on the message number to view the individual - message, or you can """) + - Link(senderurl, _('view all messages from %(esender)s')).Format() + '.' - ]) - right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2) - counter = 1 - for id in bysender[sender]: - info = mlist.GetRecord(id) - ptime, sender, subject, reason, filename, msgdata = info - # BAW: This is really the size of the message pickle, which should - # be close, but won't be exact. Sigh, good enough. - try: - size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename)) - except OSError, e: - if e.errno <> errno.ENOENT: raise - # This message must have gotten lost, i.e. it's already been - # handled by the time we got here. - mlist.HandleRequest(id, mm_cfg.DISCARD) - continue - dispsubj = Utils.oneline( - subject, Utils.GetCharSet(mlist.preferred_language)) - t = Table(css='class="helds_overview right brief"') - t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), - _('Subject:'), - Utils.websafe(dispsubj) - ]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="description strong"') - t.AddRow([' ', _('Size:'), str(size) + _(' bytes')]) - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="description strong"') - if reason: - reason = _(reason) - else: - reason = _('not available') - t.AddRow([' ', _('Reason:'), reason]) - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="description strong"') - # Include the date we received the message, if available - when = msgdata.get('received_time') - if when: - t.AddRow([' ', _('Received:'), - time.ctime(when)]) - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="description strong"') - counter += 1 - right.AddRow([t]) - stable.AddRow([left, right]) - stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, css='class="helds_overview left"') - stable.AddCellInfo(stable.GetCurrentRowIndex(), 1, css='class="helds_overview right"') - table.AddRow([stable]) - return 1 - - - -def show_sender_requests(mlist, form, sender): - bysender = helds_by_sender(mlist) - if not bysender: - return - sender_ids = bysender.get(sender) - if sender_ids is None: - # BAW: should we print an error message? - return - total = len(sender_ids) - count = 1 - for id in sender_ids: - info = mlist.GetRecord(id) - show_post_requests(mlist, id, info, total, count, form) - count += 1 - - - -def show_message_requests(mlist, form, id): - try: - id = int(id) - info = mlist.GetRecord(id) - except (ValueError, KeyError): - # BAW: print an error message? - return - show_post_requests(mlist, id, info, 1, 1, form) - - - -def show_detailed_requests(mlist, form): - all = mlist.GetHeldMessageIds() - total = len(all) - count = 1 - for id in mlist.GetHeldMessageIds(): - info = mlist.GetRecord(id) - show_post_requests(mlist, id, info, total, count, form) - count += 1 - - - -def show_post_requests(mlist, id, info, total, count, form): - # For backwards compatibility with pre 2.0beta3 - if len(info) == 5: - ptime, sender, subject, reason, filename = info - msgdata = {} - else: - ptime, sender, subject, reason, filename, msgdata = info - # Header shown on each held posting (including count of total) - msg = _('Posting Held for Approval') - if total <> 1: - msg += _(' (%(count)d of %(total)d)') - form.AddItem(Header(2, msg)) - # We need to get the headers and part of the textual body of the message - # being held. The best way to do this is to use the email Parser to get - # an actual object, which will be easier to deal with. We probably could - # just do raw reads on the file. - try: - msg = readMessage(os.path.join(mm_cfg.DATA_DIR, filename)) - except IOError, e: - if e.errno <> errno.ENOENT: - raise - form.AddItem(Paragraph(_('Message with id #%(id)d was lost.') - ).Format(css='class="Italic"')) - # BAW: kludge to remove id from requests.db. - try: - mlist.HandleRequest(id, mm_cfg.DISCARD) - except Errors.LostHeldMessage: - pass - return - except email.Errors.MessageParseError: - form.AddItem(Paragraph(_('Message with id #%(id)d is corrupted.') - ).Format(css='class="Italic"')) - # BAW: Should we really delete this, or shuttle it off for site admin - # to look more closely at? - # BAW: kludge to remove id from requests.db. - try: - mlist.HandleRequest(id, mm_cfg.DISCARD) - except Errors.LostHeldMessage: - pass - return - # Get the header text and the message body excerpt - lines = [] - chars = 0 - # A negative value means, include the entire message regardless of size - limit = mm_cfg.ADMINDB_PAGE_TEXT_LIMIT - for line in email.Iterators.body_line_iterator(msg): - lines.append(line) - chars += len(line) - if chars > limit > 0: - break - # Negative values mean display the entire message, regardless of size - if limit > 0: - body = EMPTYSTRING.join(lines)[:mm_cfg.ADMINDB_PAGE_TEXT_LIMIT] - else: - body = EMPTYSTRING.join(lines) - # Get message charset and try encode in list charset - mcset = msg.get_param('charset', 'us-ascii').lower() - lcset = Utils.GetCharSet(mlist.preferred_language) - if mcset <> lcset: - try: - body = unicode(body, mcset).encode(lcset) - except (LookupError, UnicodeError, ValueError): - pass - hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in msg.items()]) - hdrtxt = Utils.websafe(hdrtxt) - # Okay, we've reconstituted the message just fine. Now for the fun part! - t = Table() - t.AddRow([_('From: '), sender]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') - t.AddRow([(_('Subject:')), - Utils.websafe(Utils.oneline(subject, lcset))]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') - t.AddRow([_('Reason:'), _(reason)]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') - when = msgdata.get('received_time') - if when: - t.AddRow([_('Received:'), time.ctime(when)]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') - # We can't use a RadioButtonArray here because horizontal placement can be - # confusing to the user and vertical placement takes up too much - # real-estate. This is a hack! - buttons = Table() - buttons.AddRow([RadioButton(id, mm_cfg.DEFER, 1).Format() + _('Defer'), - RadioButton(id, mm_cfg.APPROVE, 0).Format() + _('Approve'), - RadioButton(id, mm_cfg.REJECT, 0).Format() + _('Reject'), - RadioButton(id, mm_cfg.DISCARD, 0).Format() + _('Discard'), - ]) - t.AddRow([_('Action:'), - buttons.Format() + - CheckBox('preserve-%d' % id, 'on', 0).Format() + - _('Preserve message for site administrator') + '
      ' + - CheckBox('forward-%d' % id, 'on', 0).Format() + - _('Additionally, forward this message to: ') + - TextBox('forward-addr-%d' % id, size=47, value=mlist.GetOwnerEmail()).Format() - ]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') - notice = msgdata.get('rejection_notice', _('[No explanation given]')) - t.AddRow([ - _('If you reject this post, please explain (optional):'), - TextArea('comment-%d' % id, rows=4, cols=EXCERPT_WIDTH, - text = Utils.wrap(_(notice), column=80)) - ]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') - t.AddRow([_('Message Headers:'), - TextArea('headers-%d' % id, hdrtxt, - rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') - t.AddRow([_('Message Excerpt:'), - TextArea('fulltext-%d' % id, Utils.websafe(body), - rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)]) - t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') - t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') - form.AddItem(t) - - - -def process_form(mlist, doc, cgidata): - senderactions = {} - # Sender-centric actions - for k in cgidata.keys(): - for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-', - 'senderforwardto-', 'senderfilterp-', 'senderfilter-', - 'senderclearmodp-', 'senderbanp-'): - if k.startswith(prefix): - action = k[:len(prefix)-1] - sender = unquote_plus(k[len(prefix):]) - value = cgidata.getvalue(k) - senderactions.setdefault(sender, {})[action] = value - # discard-all-defers - try: - discardalldefersp = cgidata.getvalue('discardalldefersp', 0) - except ValueError: - discardalldefersp = 0 - for sender in senderactions.keys(): - actions = senderactions[sender] - # Handle what to do about all this sender's held messages - try: - action = int(actions.get('senderaction', mm_cfg.DEFER)) - except ValueError: - action = mm_cfg.DEFER - if action == mm_cfg.DEFER and discardalldefersp: - action = mm_cfg.DISCARD - if action in (mm_cfg.DEFER, mm_cfg.APPROVE, - mm_cfg.REJECT, mm_cfg.DISCARD): - preserve = actions.get('senderpreserve', 0) - forward = actions.get('senderforward', 0) - forwardaddr = actions.get('senderforwardto', '') - comment = _('No reason given') - bysender = helds_by_sender(mlist) - for id in bysender.get(sender, []): - try: - mlist.HandleRequest(id, action, comment, preserve, - forward, forwardaddr) - except (KeyError, Errors.LostHeldMessage): - # That's okay, it just means someone else has already - # updated the database while we were staring at the page, - # so just ignore it - continue - # Now see if this sender should be added to one of the nonmember - # sender filters. - if actions.get('senderfilterp', 0): - try: - which = int(actions.get('senderfilter')) - except ValueError: - # Bogus form - which = 'ignore' - if which == mm_cfg.ACCEPT: - mlist.accept_these_nonmembers.append(sender) - elif which == mm_cfg.HOLD: - mlist.hold_these_nonmembers.append(sender) - elif which == mm_cfg.REJECT: - mlist.reject_these_nonmembers.append(sender) - elif which == mm_cfg.DISCARD: - mlist.discard_these_nonmembers.append(sender) - # Otherwise, it's a bogus form, so ignore it - # And now see if we're to clear the member's moderation flag. - if actions.get('senderclearmodp', 0): - try: - mlist.setMemberOption(sender, mm_cfg.Moderate, 0) - except Errors.NotAMemberError: - # This person's not a member any more. Oh well. - pass - # And should this address be banned? - if actions.get('senderbanp', 0): - if sender not in mlist.ban_list: - mlist.ban_list.append(sender) - # Now, do message specific actions - banaddrs = [] - erroraddrs = [] - for k in cgidata.keys(): - formv = cgidata[k] - if type(formv) == ListType: - continue - try: - v = int(formv.value) - request_id = int(k) - except ValueError: - continue - if v not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, - mm_cfg.DISCARD, mm_cfg.SUBSCRIBE, mm_cfg.UNSUBSCRIBE, - mm_cfg.ACCEPT, mm_cfg.HOLD): - continue - # Get the action comment and reasons if present. - commentkey = 'comment-%d' % request_id - preservekey = 'preserve-%d' % request_id - forwardkey = 'forward-%d' % request_id - forwardaddrkey = 'forward-addr-%d' % request_id - bankey = 'ban-%d' % request_id - # Defaults - comment = _('[No reason given]') - preserve = 0 - forward = 0 - forwardaddr = '' - if cgidata.has_key(commentkey): - comment = cgidata[commentkey].value - if cgidata.has_key(preservekey): - preserve = cgidata[preservekey].value - if cgidata.has_key(forwardkey): - forward = cgidata[forwardkey].value - if cgidata.has_key(forwardaddrkey): - forwardaddr = cgidata[forwardaddrkey].value - # Should we ban this address? Do this check before handling the - # request id because that will evict the record. - if cgidata.getvalue(bankey): - sender = mlist.GetRecord(request_id)[1] - if sender not in mlist.ban_list: - mlist.ban_list.append(sender) - # Handle the request id - try: - mlist.HandleRequest(request_id, v, comment, - preserve, forward, forwardaddr) - except (KeyError, Errors.LostHeldMessage): - # That's okay, it just means someone else has already updated the - # database while we were staring at the page, so just ignore it - continue - except Errors.MMAlreadyAMember, v: - erroraddrs.append(v) - except Errors.MembershipIsBanned, pattern: - sender = mlist.GetRecord(request_id)[1] - banaddrs.append((sender, pattern)) - # save the list and print the results - doc.addMessage(_('Database Updated'), css='class="message success strong"') - if erroraddrs: - for addr in erroraddrs: - doc.AddItem(`addr` + _(' is already a member') + '
      ') - if banaddrs: - for addr, patt in banaddrs: - doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '
      ') diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/confirm.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/confirm.py deleted file mode 100644 index 90801da..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/confirm.py +++ /dev/null @@ -1,752 +0,0 @@ -# Copyright (C) 2001-2005 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Confirm a pending action via URL.""" - -import signal -import cgi -import time - -from Mailman import mm_cfg -from Mailman import Errors -from Mailman import i18n -from Mailman import MailList -from Mailman import Pending -from Mailman.UserDesc import UserDesc -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -def main(): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - parts = Utils.GetPathPieces() - if not parts or len(parts) < 1: - bad_confirmation(doc) - doc.AddItem(MailmanLogo()) - print doc.Format() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - bad_confirmation(doc, _('No such list %(safelistname)s')) - doc.AddItem(MailmanLogo()) - print doc.Format() - syslog('error', 'No such list "%s": %s', listname, e) - return - - # Set the language for the list - i18n.set_language(mlist.preferred_language) - doc.set_language(mlist.preferred_language) - - # Get the form data to see if this is a second-step confirmation - cgidata = cgi.FieldStorage(keep_blank_values=1) - cookie = cgidata.getvalue('cookie') - if cookie == '': - ask_for_cookie(mlist, doc, _('Confirmation string was empty.')) - return - - if not cookie and len(parts) == 2: - cookie = parts[1] - - if len(parts) > 2: - bad_confirmation(doc) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - if not cookie: - ask_for_cookie(mlist, doc) - return - - days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1) + 0.5) - confirmurl = mlist.GetScriptURL('confirm', absolute=1) - # Avoid cross-site scripting attacks - safecookie = Utils.websafe(cookie) - badconfirmstr = _('''Invalid confirmation string:%(safecookie)s.''') + '

      ' - badconfirmstr += '

      ' + _('''Note that confirmation strings expire approximately %(days)s days after the initial subscription request. If your confirmation has expired, please try to re-submit your subscription. Otherwise, re-enter your confirmation string.''') - - content = mlist.pend_confirm(cookie, expunge=False) - if content is None: - bad_confirmation(doc, badconfirmstr) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - try: - if content[0] == Pending.SUBSCRIPTION: - if cgidata.getvalue('cancel'): - subscription_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - subscription_confirm(mlist, doc, cookie, cgidata) - else: - subscription_prompt(mlist, doc, cookie, content[1]) - elif content[0] == Pending.UNSUBSCRIPTION: - try: - if cgidata.getvalue('cancel'): - unsubscription_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - unsubscription_confirm(mlist, doc, cookie) - else: - unsubscription_prompt(mlist, doc, cookie, *content[1:]) - except Errors.NotAMemberError: - doc.addError(_('The address requesting unsubscription is not a member of the mailing list. Perhaps you have already been unsubscribed, e.g. by the list administrator?')) - # Expunge this record from the pending database. - expunge(mlist, cookie) - elif content[0] == Pending.CHANGE_OF_ADDRESS: - if cgidata.getvalue('cancel'): - addrchange_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - addrchange_confirm(mlist, doc, cookie) - else: - # Watch out for users who have unsubscribed themselves in the - # meantime! - try: - addrchange_prompt(mlist, doc, cookie, *content[1:]) - except Errors.NotAMemberError: - doc.addError(_("""The address requesting to be changed has been subsequently unsubscribed. This request has been cancelled.""")) - # Expunge this record from the pending database. - expunge(mlist, cookie) - elif content[0] == Pending.HELD_MESSAGE: - if cgidata.getvalue('cancel'): - heldmsg_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - heldmsg_confirm(mlist, doc, cookie) - else: - heldmsg_prompt(mlist, doc, cookie, *content[1:]) - elif content[0] == Pending.RE_ENABLE: - if cgidata.getvalue('cancel'): - reenable_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - reenable_confirm(mlist, doc, cookie) - else: - reenable_prompt(mlist, doc, cookie, *content[1:]) - else: - bad_confirmation(doc, _('System error, bad content: %(content)s')) - except Errors.MMBadConfirmation: - bad_confirmation(doc, badconfirmstr) - - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def bad_confirmation(doc, extra=''): - title = _('Bad confirmation string') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(extra)) - - -def expunge(mlist, cookie): - # Expunge this record from the list's pending database. This requires - # that the list lock be acquired, however the list doesn't need to be - # saved because this operation doesn't touch the config.pck file. - mlist.Lock() - try: - mlist.pend_confirm(cookie, expunge=True) - finally: - mlist.Unlock() - - - -def ask_for_cookie(mlist, doc, extra=''): - title = _('Enter confirmation cookie') - doc.SetTitle(title) - if extra: - doc.AddItem(Div(Paragraph(extra)).Format(css='class="message error strong"')) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_("""Please enter the confirmation string (i.e. cookie) that you received in your email message, in the box below. Then hit the Submit button to proceed to the next confirmation step."""))) - form = Form(mlist.GetScriptURL('confirm', 1)) - table = Table() - - # Add cookie entry box - table.AddRow([_('Confirmation string:'), - TextBox('cookie')]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow([SubmitButton('submit_cookie', _('Submit'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') - form.AddItem(table) - doc.AddItem(form) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def subscription_prompt(mlist, doc, cookie, userdesc): - email = userdesc.address - password = userdesc.password - digest = userdesc.digest - lang = userdesc.language - name = Utils.uncanonstr(userdesc.fullname, lang) - i18n.set_language(lang) - doc.set_language(lang) - form = Form(mlist.GetScriptURL('confirm', 1)) - table = Table() - - title = _('Confirm subscription request') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - - listname = mlist.real_name - # This is the normal, no-confirmation required results text. - # - # We do things this way so we don't have to reformat this paragraph, which - # would mess up translations. If you modify this text for other reasons, - # please refill the paragraph, and clean up the logic. - - doc.AddItem(Paragraph(_('''Your confirmation is required in order to complete the subscription request to the mailing list %(listname)s. Your subscription settings are shown below; make any necessary changes and hit Subscribe to complete the confirmation process. Once you've confirmed your subscription request, you will be shown your account options page which you can use to further customize your membership options.'''))) - - doc.AddItem(Paragraph(_('''Note: your password will be emailed to you once your subscription is confirmed. You can change it by visiting your personal options page.'''))) - - doc.AddItem(Paragraph(_('''Or hit Cancel my subscription request if you no longer want to subscribe to this list.'''))) - - if mlist.subscribe_policy in (2, 3): - # Confirmation is required - AddItem(Paragraph(_("""Your confirmation is required in order to continue with the subscription request to the mailing list %(listname)s. Your subscription settings are shown below; make any necessary changes and hit Subscribe to list ... to complete the confirmation process. Once you've confirmed your subscription request, the moderator must approve or reject your membership request. You will receive notice of their decision."""))) - - doc.AddItem(Paragraph(_('''Note: your password will be emailed to you once your subscription is confirmed. You can change it by visiting your personal options page.'''))) - - doc.AddItem(Paragraph(_('''Or, if you've changed your mind and do not want to subscribe to this mailing list, you can hit Cancel my subscription request.'''))) - - table.AddRow([_('Your email address:'), email]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - table.AddRow([_('Your real name:'), - TextBox('realname', name)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') -## table.AddRow([Label(_('Password:')), -## PasswordBox('password', password)]) -## table.AddRow([Label(_('Password (confirm):')), -## PasswordBox('pwconfirm', password)]) - # Only give them a choice to receive digests if they actually have a - # choice . - if mlist.nondigestable and mlist.digestable: - table.AddRow([_('Receive digests?'), - RadioButtonArray('digests', (_('No'), _('Yes')), - checked=digest, values=(0, 1))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - langs = mlist.GetAvailableLanguages() - values = [_(Utils.GetLanguageDescr(l)) for l in langs] - try: - selected = langs.index(lang) - except ValueError: - selected = lang.index(mlist.preferred_language) - table.AddRow([_('Preferred language:'), - SelectOptions('language', langs, values, selected)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([ - SubmitButton('cancel', _('Cancel my subscription request')), - SubmitButton('submit', _('Subscribe to list %(listname)s')) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') - form.AddItem(table) - doc.AddItem(form) - - - -def subscription_cancel(mlist, doc, cookie): - mlist.Lock() - try: - # Discard this cookie - userdesc = mlist.pend_confirm(cookie)[1] - finally: - mlist.Unlock() - lang = userdesc.language - i18n.set_language(lang) - doc.set_language(lang) - title = _('Subscription request canceled') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_('You have canceled your subscription request.'))) - - - -def subscription_confirm(mlist, doc, cookie, cgidata): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - listname = mlist.real_name - mlist.Lock() - try: - try: - # Some pending values may be overridden in the form. email of - # course is hardcoded. ;) - lang = cgidata.getvalue('language') - if not Utils.IsLanguage(lang): - lang = mlist.preferred_language - i18n.set_language(lang) - doc.set_language(lang) - if cgidata.has_key('digests'): - try: - digest = int(cgidata.getvalue('digests')) - except ValueError: - digest = None - else: - digest = None - userdesc = mlist.pend_confirm(cookie, expunge=False)[1] - fullname = cgidata.getvalue('realname', None) - if fullname is not None: - fullname = Utils.canonstr(fullname, lang) - overrides = UserDesc(fullname=fullname, digest=digest, lang=lang) - userdesc += overrides - op, addr, pw, digest, lang = mlist.ProcessConfirmation( - cookie, userdesc) - except Errors.MMNeedApproval: - title = _('Awaiting moderator approval') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_("""You have successfully confirmed your subscription request to the mailing list %(listname)s, however final approval is required from the list moderator before you will be subscribed. Your request has been forwarded to the list moderator, and you will be notified of the moderator's decision."""))) - except Errors.NotAMemberError: - bad_confirmation(doc, _('''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) - except Errors.MMAlreadyAMember: - doc.addError(_("You are already a member of this mailing list!")) - except Errors.MembershipIsBanned: - owneraddr = mlist.GetOwnerEmail() - doc.addError(_("""You are currently banned from subscribing to this list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.""")) - except Errors.HostileSubscriptionError: - doc.addError(_("""You were not invited to this mailing list. The invitation has been discarded, and both list administrators have been alerted.""")) - else: - # Use the user's preferred language - i18n.set_language(lang) - doc.set_language(lang) - # The response - listname = mlist.real_name - title = _('Subscription request confirmed') - optionsurl = mlist.GetOptionsURL(addr, absolute=1) - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_('''You have successfully confirmed your subscription request for "%(addr)s" to the %(listname)s mailing list. A separate confirmation message will be sent to your email address, along with your password, and other useful information and links.'''))) - - doc.AddItem(Paragraph(_('''You can now proceed to your membership login page.'''))) - mlist.Save() - finally: - mlist.Unlock() - - - -def unsubscription_cancel(mlist, doc, cookie): - # Expunge this record from the pending database - expunge(mlist, cookie) - title = _('Unsubscription request canceled') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_('You have canceled your unsubscription request.'))) - - - -def unsubscription_confirm(mlist, doc, cookie): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - mlist.Lock() - try: - try: - # Do this in two steps so we can get the preferred language for - # the user who is unsubscribing. - op, addr = mlist.pend_confirm(cookie, expunge=False) - lang = mlist.getMemberLanguage(addr) - i18n.set_language(lang) - doc.set_language(lang) - op, addr = mlist.ProcessConfirmation(cookie) - except Errors.NotAMemberError: - bad_confirmation(doc, _('''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) - else: - # The response - listname = mlist.real_name - title = _('Unsubscription request confirmed') - listinfourl = mlist.GetScriptURL('listinfo', absolute=1) - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_("""You have successfully unsubscribed from the %(listname)s mailing list. You can now visit the list's main information page."""))) - mlist.Save() - finally: - mlist.Unlock() - - - -def unsubscription_prompt(mlist, doc, cookie, addr): - # Set language - lang = mlist.getMemberLanguage(addr) - i18n.set_language(lang) - doc.set_language(lang) - - # Set title - title = _('Confirm unsubscription request') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - - # Set listname and fullname - listname = mlist.real_name - fullname = mlist.getMemberName(addr) - if fullname is None: - fullname = _('Not available.') - else: - fullname = Utils.uncanonstr(fullname, lang) - doc.AddItem(Paragraph(_('Your confirmation is required in order to complete the unsubscription request from the mailing list %(listname)s. You are currently subscribed with:'))) - doc.AddItem(''' -

        -
      • ''' + _('Real name:') + ''' ''' + fullname + '''
      • -
      • ''' + _('Email address:') + ''' ''' + addr + '''
      • -
      - ''') - doc.AddItem(Paragraph(_('''Hit the Unsubscribe button below to complete the confirmation process.'''))) - doc.AddItem(Paragraph(_('''Or hit Cancel and discard to cancel this unsubscription request.'''))) - - # Set Form - form = Form(mlist.GetScriptURL('confirm', 1)) - table = Table() - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([SubmitButton('submit', _('Unsubscribe')), - SubmitButton('cancel', _('Cancel and discard'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') - - form.AddItem(table) - doc.AddItem(form) - - - -def addrchange_cancel(mlist, doc, cookie): - # Expunge this record from the pending database - expunge(mlist, cookie) - title = 'Change of address request canceled' - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_('You have canceled your change of address request.'))) - - - -def addrchange_confirm(mlist, doc, cookie): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - mlist.Lock() - try: - try: - # Do this in two steps so we can get the preferred language for - # the user who is unsubscribing. - op, oldaddr, newaddr, globally = mlist.pend_confirm( - cookie, expunge=False) - lang = mlist.getMemberLanguage(oldaddr) - i18n.set_language(lang) - doc.set_language(lang) - op, oldaddr, newaddr = mlist.ProcessConfirmation(cookie) - except Errors.NotAMemberError: - bad_confirmation(doc, _('''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) - except Errors.MembershipIsBanned: - owneraddr = mlist.GetOwnerEmail() - realname = mlist.real_name - doc.addError(_("""%(newaddr)s is banned from subscribing to the %(realname)s list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.""")) - else: - # The response - listname = mlist.real_name - title = _('Change of address request confirmed') - optionsurl = mlist.GetOptionsURL(newaddr, absolute=1) - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_("""You have successfully changed your address on the %(listname)s mailing list from %(oldaddr)s to %(newaddr)s."""))) - doc.AddItem(Paragraph(_('''You can now proceed to your membership login page.'''))) - mlist.Save() - finally: - mlist.Unlock() - - - -def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): - # Set Language - lang = mlist.getMemberLanguage(oldaddr) - i18n.set_language(lang) - doc.set_language(lang) - # Set Title - title = _('Confirm change of address request') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - # Set Listname and Fullname - listname = mlist.real_name - fullname = mlist.getMemberName(oldaddr) - if fullname is None: - fullname = _('Not available') - else: - fullname = Utils.uncanonstr(fullname, lang) - if globally: - globallys = _('globally') - else: - globallys = '' - - # Set Description - doc.AddItem(Paragraph(_('''Your confirmation is required in order to complete the change of address request for the mailing list %(listname)s. You are currently subscribed with:'''))) - doc.AddItem(_(''' -
        -
      • ''' + _('Real name:') + ''' %(fullname)s
      • -
      • ''' + _('Old email address:') + ''' %(oldaddr)s
      • -
      - ''')) - doc.AddItem(Paragraph(_('''and you have requested to %(globallys)s change your email address to:'''))) - doc.AddItem(_(''' -
        -
      • ''' + _('New email address:') + ''' %(newaddr)s
      • -
      - ''')) - doc.AddItem(Paragraph(_('''Hit the Change address button below to complete the confirmation process. Or hit Cancel and discard to cancel this change of address request.'''))) - - form = Form(mlist.GetScriptURL('confirm', 1)) - table = Table() - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([SubmitButton('submit', _('Change address')), - SubmitButton('cancel', _('Cancel and discard'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') - form.AddItem(table) - doc.AddItem(form) - - - -def heldmsg_cancel(mlist, doc, cookie): - title = _('Continue awaiting approval') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - # Expunge this record from the pending database. - expunge(mlist, cookie) - doc.AddItem(Paragraph('''Okay, the list moderator will still have the opportunity to approve or reject this message.''')) - - - -def heldmsg_confirm(mlist, doc, cookie): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - mlist.Lock() - try: - try: - # Do this in two steps so we can get the preferred language for - # the user who posted the message. - op, id = mlist.pend_confirm(cookie) - ign, sender, msgsubject, ign, ign, ign = mlist.GetRecord(id) - subject = Utils.websafe(msgsubject) - lang = mlist.getMemberLanguage(sender) - i18n.set_language(lang) - doc.set_language(lang) - # Discard the message - mlist.HandleRequest(id, mm_cfg.DISCARD, - _('Sender discarded message via web.')) - except Errors.LostHeldMessage: - bad_confirmation(doc, Paragraph(_('''The held message with the Subject: header %(subject)s could not be found. The most likely reason for this is that the list moderator has already approved or rejected the message. You were not able to cancel it in time.'''))) - else: - # The response - listname = mlist.real_name - title = _('Posted message canceled') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_('''You have successfully canceled the posting of your message with the Subject: %(subject)s to the mailing list %(listname)s.'''))) - mlist.Save() - finally: - mlist.Unlock() - - - -def heldmsg_prompt(mlist, doc, cookie, id): - title = _('Cancel held message posting') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - # Blarg. The list must be locked in order to interact with the ListAdmin - # database, even for read-only. See the comment in admin.py about the - # need for the signal handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - # Get the record, but watch for KeyErrors which mean the admin has already - # disposed of this message. - mlist.Lock() - try: - try: - data = mlist.GetRecord(id) - except KeyError: - data = None - finally: - mlist.Unlock() - - if data is None: - bad_confirmation(doc, Paragraph(_("""The held message you were referred to has already been handled by the list administrator."""))) - return - - # Unpack the data and present the confirmation message - ign, sender, msgsubject, givenreason, ign, ign = data - # Now set the language to the sender's preferred. - lang = mlist.getMemberLanguage(sender) - i18n.set_language(lang) - doc.set_language(lang) - subject = Utils.websafe(msgsubject) - reason = Utils.websafe(_(givenreason)) - listname = mlist.real_name - doc.AddItem(Paragraph(_('Your confirmation is required in order to cancel the posting of your message to the mailing list %(listname)s:'))) - doc.AddItem(''' -
      • ''' + _('Sender:') + ''' ''' + sender + '''
      • -
      • ''' + _('Subject:') + ''' ''' + subject + '''
      • -
      • ''' + _('Reason:') + ''' ''' + reason + '''
      • -
      - ''') - doc.AddItem(Paragraph('''Hit the Cancel posting button to discard the posting. Or hit the Continue awaiting approval button to continue to allow the list moderator to approve or reject the message.''')) - form = Form(mlist.GetScriptURL('confirm', 1)) - table = Table() - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([SubmitButton('submit', _('Cancel posting')), - SubmitButton('cancel', _('Continue awaiting approval'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') - - form.AddItem(table) - doc.AddItem(form) - - - -def reenable_cancel(mlist, doc, cookie): - # Don't actually discard this cookie, since the user may decide to - # re-enable their membership at a future time, and we may be sending out - # future notifications with this cookie value. - title = _('Membership re-enabeling canceled') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_("""You have canceled the re-enabling of your membership. If we continue to receive bounces from your address, it could be deleted from this mailing list."""))) - - - -def reenable_confirm(mlist, doc, cookie): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - mlist.Lock() - try: - try: - # Do this in two steps so we can get the preferred language for - # the user who is unsubscribing. - op, listname, addr = mlist.pend_confirm(cookie, expunge=False) - lang = mlist.getMemberLanguage(addr) - i18n.set_language(lang) - doc.set_language(lang) - op, addr = mlist.ProcessConfirmation(cookie) - except Errors.NotAMemberError: - bad_confirmation(doc, _('''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) - else: - # The response - listname = mlist.real_name - optionsurl = mlist.GetOptionsURL(addr, absolute=1) - title = _('Membership re-enabled') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_("""You have successfully re-enabled your membership in the %(listname)s mailing list. You can now visit your member options page."""))) - mlist.Save() - finally: - mlist.Unlock() - - - -def reenable_prompt(mlist, doc, cookie, list, member): - title = _('Re-enable mailing list membership') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - - lang = mlist.getMemberLanguage(member) - i18n.set_language(lang) - doc.set_language(lang) - - realname = mlist.real_name - info = mlist.getBounceInfo(member) - - if not info: - listinfourl = mlist.GetScriptURL('listinfo', absolute=1) - # They've already be unsubscribed - doc.AddItem(Paragraph(_("""We're sorry, but you have already been unsubscribed from this mailing list. To re-subscribe, please visit the list information page."""))) - return - - date = time.strftime('%A, %B %d, %Y', - time.localtime(time.mktime(info.date + (0,)*6))) - daysleft = int(info.noticesleft * - mlist.bounce_you_are_disabled_warnings_interval / - mm_cfg.days(1)) - # BAW: for consistency this should be changed to 'fullname' or the above - # 'fullname's should be changed to 'username'. Don't want to muck with - # the i18n catalogs though. - username = mlist.getMemberName(member) - if username is None: - username = _('Not available') - else: - username = Utils.uncanonstr(username, lang) - - doc.AddItem(Paragraph(_('Your membership in the %(realname)s mailing list is currently disabled due to excessive bounces. Your confirmation is required in order to re-enable delivery to your address. We have the following information on file:'))) - doc.AddItem(''' -
      • ''' + _('Member address:') + ''' ''' + member + '''
      • -
      • ''' + _('Member name:') + ''' ''' + username + '''
      • -
      • ''' + _('Last bounce received on:') + ''' ''' + date + '''
      • -
      • ''' + _('Approximate number of days before you are permanently removed from this list:') + ''' ''' + daysleft + '''
      • -
      - ''') - doc.AddItem(Paragraph(_('''Hit the Re-enable membership button to resume receiving postings from the mailing list. Or hit the Cancel button to defer re-enabling your membership.'''))) - - form = Form(mlist.GetScriptURL('confirm', 1)) - table = Table() - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([SubmitButton('submit', _('Re-enable membership')), - SubmitButton('cancel', _('Cancel'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') - - form.AddItem(table) - doc.AddItem(form) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/create.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/create.py deleted file mode 100755 index 95641f6..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/create.py +++ /dev/null @@ -1,416 +0,0 @@ -# Copyright (C) 2001-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Create mailing lists through the web.""" - -import sys -import os -import signal -import cgi -import sha -from types import ListType - -from Mailman import mm_cfg -from Mailman import MailList -from Mailman import Message -from Mailman import Errors -from Mailman import i18n -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - - -def main(): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - cgidata = cgi.FieldStorage() - parts = Utils.GetPathPieces() - if parts: - # Bad URL specification - title = _('Bad URL specification') - doc.SetTitle(title) - doc.AddItem(Div(Paragraph(title)).Format(css='class="message error strong"')) - syslog('error', 'Bad URL specification: %s', parts) - elif cgidata.has_key('doit'): - # We must be processing the list creation request - process_request(doc, cgidata) - elif cgidata.has_key('clear'): - request_creation(doc) - else: - # Put up the list creation request form - request_creation(doc) - # Always add the footer and print the document - doc.AddItem(Paragraph( - _('Return to the ') + - Link(Utils.ScriptURL('listinfo'), - _('general list overview')).Format() + - '
      ' + _('Return to the ') + - Link(Utils.ScriptURL('admin'), - _('administrative list overview')).Format()).Format()) - doc.AddItem(MailmanLogo()) - print doc.Format() - - - -def process_request(doc, cgidata): - # Lowercase the listname since this is treated as the "internal" name. - listname = cgidata.getvalue('listname', '').strip().lower() - owner = cgidata.getvalue('owner', '').strip() - try: - autogen = int(cgidata.getvalue('autogen', '0')) - except ValueError: - autogen = 0 - try: - notify = int(cgidata.getvalue('notify', '0')) - except ValueError: - notify = 0 - try: - moderate = int(cgidata.getvalue('moderate', - mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) - except ValueError: - moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION - - password = cgidata.getvalue('password', '').strip() - confirm = cgidata.getvalue('confirm', '').strip() - auth = cgidata.getvalue('auth', '').strip() - langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) - - if not isinstance(langs, ListType): - langs = [langs] - # Sanity check - safelistname = Utils.websafe(listname) - if '@' in listname: - request_creation(doc, cgidata, - _('List name must not include "@": %(safelistname)s')) - return - if Utils.list_exists(listname): - # BAW: should we tell them the list already exists? This could be - # used to mine/guess the existance of non-advertised lists. Then - # again, that can be done in other ways already, so oh well. - request_creation(doc, cgidata, - _('List already exists: %(safelistname)s')) - return - if not listname: - request_creation(doc, cgidata, - _('You forgot to enter the list name')) - return - if not owner: - request_creation(doc, cgidata, - _('You forgot to specify the list owner')) - return - - if autogen: - if password or confirm: - request_creation( - doc, cgidata, - _('Leave the initial password (and confirmation) fields blank if you want Mailman to autogenerate the list passwords.')) - return - password = confirm = Utils.MakeRandomPassword( - mm_cfg.ADMIN_PASSWORD_LENGTH) - else: - if password <> confirm: - request_creation(doc, cgidata, - _('Initial list passwords do not match')) - return - if not password: - request_creation( - doc, cgidata, - # The little tag is used so that this string - # differs from the one in bin/newlist. The former is destined - # for the web while the latter is destined for email, so they - # must be different entries in the message catalog. - _('The list password cannot be empty')) - return - # The authorization password must be non-empty, and it must match either - # the list creation password or the site admin password - ok = 0 - if auth: - ok = Utils.check_global_password(auth, 0) - if not ok: - ok = Utils.check_global_password(auth) - if not ok: - request_creation( - doc, cgidata, - _('You are not authorized to create new mailing lists')) - return - # Make sure the web hostname matches one of our virtual domains - hostname = Utils.get_domain() - if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ - not mm_cfg.VIRTUAL_HOSTS.has_key(hostname): - safehostname = Utils.websafe(hostname) - request_creation(doc, cgidata, - _('Unknown virtual host: %(safehostname)s')) - return - emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST) - # We've got all the data we need, so go ahead and try to create the list - # See admin.py for why we need to set up the signal handler. - mlist = MailList.MailList() - - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - mlist.Unlock() - # ...and ensure we exit, otherwise race conditions could cause us to - # enter MailList.Save() while we're in the unlocked state, and that - # could be bad! - sys.exit(0) - - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) - - pw = sha.new(password).hexdigest() - # Guarantee that all newly created files have the proper permission. - # proper group ownership should be assured by the autoconf script - # enforcing that all directories have the group sticky bit set - oldmask = os.umask(002) - try: - try: - mlist.Create(listname, owner, pw, langs, emailhost) - finally: - os.umask(oldmask) - except Errors.EmailAddressError, e: - if e.args: - s = Utils.websafe(e.args[0]) - else: - s = Utils.websafe(owner) - request_creation(doc, cgidata, - _('Bad owner email address: %(s)s')) - return - except Errors.MMListAlreadyExistsError: - # MAS: List already exists so we don't need to websafe it. - request_creation(doc, cgidata, - _('List already exists: %(listname)s')) - return - except Errors.BadListNameError, e: - if e.args: - s = Utils.websafe(e.args[0]) - else: - s = Utils.websafe(listname) - request_creation(doc, cgidata, - _('Illegal list name: %(s)s')) - return - except Errors.MMListError: - request_creation( - doc, cgidata, - _('''Some unknown error occurred while creating the list. Please contact the site administrator for assistance.''')) - return - - # Initialize the host_name and web_page_url attributes, based on - # virtual hosting settings and the request environment variables. - mlist.default_member_moderation = moderate - mlist.web_page_url = mm_cfg.DEFAULT_URL_PATTERN % hostname - mlist.host_name = emailhost - mlist.Save() - finally: - # Now be sure to unlock the list. It's okay if we get a signal here - # because essentially, the signal handler will do the same thing. And - # unlocking is unconditional, so it's not an error if we unlock while - # we're already unlocked. - mlist.Unlock() - - # Now do the MTA-specific list creation tasks - if mm_cfg.MTA: - modname = 'Mailman.MTA.' + mm_cfg.MTA - __import__(modname) - sys.modules[modname].create(mlist, cgi=1) - - # And send the notice to the list owner. - if notify: - siteowner = Utils.get_site_email(mlist.host_name, 'owner') - text = Utils.maketext( - 'newlist.txt', - {'listname' : listname, - 'password' : password, - 'admin_url' : mlist.GetScriptURL('admin', absolute=1), - 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), - 'requestaddr' : mlist.GetRequestEmail(), - 'siteowner' : siteowner, - }, mlist=mlist) - msg = Message.UserNotification( - owner, siteowner, - _('Your new mailing list: %(listname)s'), - text, mlist.preferred_language) - msg.send(mlist) - - # Success! - listinfo_url = mlist.GetScriptURL('listinfo', absolute=1) - admin_url = mlist.GetScriptURL('admin', absolute=1) - create_url = Utils.ScriptURL('create') - - title = _('Mailing list creation results') - doc.SetTitle(title) - #doc.AddItem(Header(1, title)) - doc.addMessage(_('''You have successfully created the mailing list %(listname)s. A notification has been sent to the list owner %(owner)s.'''), css='class="message success strong"') - doc.AddItem(Paragraph(_('You can now:'))) - ullist = UnorderedList() - ullist.AddItem(Link(listinfo_url, _("Visit the list's info page"))) - ullist.AddItem(Link(admin_url, _("Visit the list's admin page"))) - ullist.AddItem(Link(create_url, _('Create another list'))) - doc.AddItem(ullist) - - -# Because the cgi module blows -class Dummy: - def getvalue(self, name, default): - return default -dummy = Dummy() - - - -def request_creation(doc, cgidata=dummy, errmsg=None): - # What virtual domain are we using? - hostname = Utils.get_domain() - # Set up the document - title = _('Create a %(hostname)s Mailing List') - table = Table() - doc.SetTitle(title) - # Add any error message - if errmsg: - doc.AddItem(Div(Paragraph(errmsg)).Format(css='class="message error strong"')) - - # Add header - doc.AddItem(Header(1, title)) - - # Add description - doc.AddItem(Paragraph(_('''You can create a new mailing list by entering the relevant information into the form below. The name of the mailing list will be used as the primary address for posting messages to the list, so it should be lowercased. You will not be able to change this once the list is created.'''))) - - doc.AddItem(Paragraph(_('''You also need to enter the email address of the initial list owner. Once the list is created, the list owner will be given notification, along with the initial list password. The list owner will then be able to modify the password and add or remove additional list owners.'''))) - - doc.AddItem(Paragraph(_('''If you want Mailman to automatically generate the initial list admin password, click on `Yes' in the autogenerate field below, and leave the initial list password fields empty.'''))) - - doc.AddItem(Paragraph(_('''You must have the proper authorization to create new mailing lists. Each site should have a list creator's password, which you can enter in the field at the bottom. Note that the site administrator's password can also be used for authentication.'''))) - - # Build the form for the necessary input - GREY = mm_cfg.WEB_ADMINITEM_COLOR - form = Form(Utils.ScriptURL('create')) - table = Table() - - table.AddRow([Header(3, _('List Identity'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') - - listname = cgidata.getvalue('listname', '') - # MAS: Don't websafe twice. TextBox does it. - table.AddRow([_('Name of list:'), - TextBox('listname', listname)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - owner = cgidata.getvalue('owner', '') - # MAS: Don't websafe twice. TextBox does it. - table.AddRow([_('Initial list owner address:'), - TextBox('owner', owner)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - try: - autogen = int(cgidata.getvalue('autogen', '0')) - except ValueError: - autogen = 0 - table.AddRow([_('Auto-generate initial list password?'), - RadioButtonArray('autogen', (_('No'), _('Yes')), - checked=autogen, - values=(0, 1))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - safepasswd = Utils.websafe(cgidata.getvalue('password', '')) - table.AddRow([_('Initial list password:'), - PasswordBox('password', safepasswd)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - safeconfirm = Utils.websafe(cgidata.getvalue('confirm', '')) - table.AddRow([_('Confirm initial password:'), - PasswordBox('confirm', safeconfirm)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - try: - notify = int(cgidata.getvalue('notify', '1')) - except ValueError: - notify = 1 - try: - moderate = int(cgidata.getvalue('moderate', - mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) - except ValueError: - moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION - - table.AddRow([Header(3,_('List Characteristics'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') - - table.AddRow([ - _("""Should new members be quarantined before they are allowed to post unmoderated to this list? Answer Yes to hold new member postings for moderator approval by default."""), - RadioButtonArray('moderate', (_('No'), _('Yes')), - checked=moderate, - values=(0,1))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - # Create the table of initially supported languages, sorted on the long - # name of the language. - revmap = {} - for key, (name, charset) in mm_cfg.LC_DESCRIPTIONS.items(): - revmap[_(name)] = key - langnames = revmap.keys() - langnames.sort() - langs = [] - for name in langnames: - langs.append(revmap[name]) - try: - langi = langs.index(mm_cfg.DEFAULT_SERVER_LANGUAGE) - except ValueError: - # Someone must have deleted the servers's preferred language. Could - # be other trouble lurking! - langi = 0 - # BAW: we should preserve the list of checked languages across form - # invocations. - checked = [0] * len(langs) - checked[langi] = 1 - deflang = _(Utils.GetLanguageDescr(mm_cfg.DEFAULT_SERVER_LANGUAGE)) - table.AddRow([_('Initial list of supported languages.') + - Paragraph(_('''Note that if you do not select at least one initial language, the list will use the server default language of %(deflang)s.''')).Format(), - CheckBoxArray('langs', - [_(Utils.GetLanguageDescr(L)) for L in langs], - checked=checked, - values=langs)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow([_('Send "list created" email to list owner?'), - RadioButtonArray('notify', (_('No'), _('Yes')), - checked=notify, - values=(0, 1))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([_("List creator's (authentication) password:"), - PasswordBox('auth')]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow([SubmitButton('doit', _('Create List')), - SubmitButton('clear', _('Clear Form'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="center"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center"') - - form.AddItem(table) - doc.AddItem(form) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/edithtml.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/edithtml.py deleted file mode 100755 index a7e6998..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/edithtml.py +++ /dev/null @@ -1,188 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Script which implements admin editing of the list's html templates.""" - -import os -import cgi -import errno -import re - -from Mailman import Utils -from Mailman import MailList -from Mailman.htmlformat import * -from Mailman.HTMLFormatter import HTMLFormatter -from Mailman import Errors -from Mailman.Cgi import Auth -from Mailman.Logging.Syslog import syslog -from Mailman import i18n - -_ = i18n._ - - - -def main(): - # Trick out pygettext since we want to mark template_data as translatable, - # but we don't want to actually translate it here. - def _(s): - return s - - template_data = ( - ('listinfo.html', _('General list information page')), - ('subscribe.html', _('Subscribe results page')), - ('options.html', _('User specific options page')), - ('subscribeack.txt', _('Welcome email text file')), - ) - - _ = i18n._ - doc = Document() - - # Set up the system default language - i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - parts = Utils.GetPathPieces() - if not parts: - title = _('List name is required') - doc.SetTitle(title) - doc.AddItem(Div(Paragraph( - _('%(title)s')) - ).Format(css='class="message error strong"')) - doc.AddItem(MailmanLogo()) - print doc.Format() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - title = _('No such list') - doc.SetTitle(title) - doc.AddItem(Div(Header(3, title), - Paragraph( - _('The %(safelistname)s mailing list does not exist.')) - ).Format(css='class="message error"')) - doc.AddItem(MailmanLogo()) - print doc.Format() - syslog('error', 'No such list "%s": %s', listname, e) - return - - # Now that we have a valid list, set the language to its default - i18n.set_language(mlist.preferred_language) - doc.set_language(mlist.preferred_language) - - # Must be authenticated to get any farther - cgidata = cgi.FieldStorage() - - # Editing the html for a list is limited to the list admin and site admin. - if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - cgidata.getvalue('adminpw', '')): - if cgidata.has_key('admlogin'): - # This is a re-authorization attempt - msg = Div(Paragraph(_('Authorization failed.'))).Format(css='class="message error strong"') - else: - msg = '' - Auth.loginpage(mlist, 'admin', msg=msg) - return - - realname = mlist.real_name - if len(parts) > 1: - template_name = parts[1] - for (template, info) in template_data: - if template == template_name: - template_info = _(info) - doc.SetTitle(_('Public Templates')) - break - else: - # Avoid cross-site scripting attacks - safetemplatename = Utils.websafe(template_name) - doc.SetTitle(_('Invalid Template')) - doc.AddItem(Div(Header(3, _('Invalid Template')), - Paragraph(_('%(safetemplatename)s is not a valid template.')) - ).Format(css='class="message error"')) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - else: - title = _('Public Templates') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_('Select template to edit:'))) - template_list = UnorderedList() - for (template, info) in template_data: - l = Link(mlist.GetScriptURL('edithtml') + '/' + template, _(info)) - template_list.AddItem(l) - doc.AddItem(template_list) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - try: - if cgidata.keys(): - ChangeHTML(mlist, cgidata, template_name, doc) - FormatHTML(mlist, doc, template_name, template_info) - - finally: - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def FormatHTML(mlist, doc, template_name, template_info): - realname = mlist.real_name - title = _(template_info) - doc.AddItem(Header(1, title)) - - form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name) - text = Utils.maketext(template_name, raw=1, mlist=mlist) - # MAS: Don't websafe twice. TextArea does it. - form.AddItem(Paragraph(TextArea('html_code', text, rows=40, cols=75))) - form.AddItem(Paragraph( - _('When you are done making changes...'), - SubmitButton('submit', _('Submit Changes')).Format())) - doc.AddItem(form) - - - -def ChangeHTML(mlist, cgi_info, template_name, doc): - if not cgi_info.has_key('html_code'): - doc.AddItem(Div(Header(3, _('Template Unchanged.')), - Paragraph(_("Can't have empty template."))).Format(css='class="message error"')) - return - code = cgi_info['html_code'].value - code = re.sub(r'<([/]?script.*?)>', r'<\1>', code) - langdir = os.path.join(mlist.fullpath(), mlist.preferred_language) - - # Make sure the directory exists - omask = os.umask(0) - try: - try: - os.mkdir(langdir, 02775) - except OSError, e: - if e.errno <> errno.EEXIST: raise - finally: - os.umask(omask) - fp = open(os.path.join(langdir, template_name), 'w') - try: - fp.write(code) - finally: - fp.close() - - doc.AddItem(Div(Paragraph(_('Template successfully updated.'))).Format(css='class="message success strong"')) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/listinfo.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/listinfo.py deleted file mode 100755 index 346cd3c..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/listinfo.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright (C) 1998-2003 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Produce listinfo page, primary web entry-point to mailing lists. -""" - -# No lock needed in this script, because we don't change data. - -import os -import cgi - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MailList -from Mailman import Errors -from Mailman import i18n -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - - -def main(): - parts = Utils.GetPathPieces() - if not parts: - listinfo_overview() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - listinfo_overview(_('No such list %(safelistname)s')) - syslog('error', 'No such list "%s": %s', listname, e) - return - - # See if the user want to see this page in other language - cgidata = cgi.FieldStorage() - language = cgidata.getvalue('language') - if not Utils.IsLanguage(language): - language = mlist.preferred_language - i18n.set_language(language) - list_listinfo(mlist, language) - - -def listinfo_overview(msg=''): - # Present the general listinfo overview - hostname = Utils.get_domain() - # Set up the document and assign it the correct language. The only one we - # know about at the moment is the server's default. - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - legend = _('General Information') + ' - ' + _('Mailing Lists') - doc.SetTitle(legend) - - table = Table() - #table.AddRow([Center(Header(2, legend))]) - #table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - # bgcolor=mm_cfg.WEB_HEADER_COLOR) - - # Skip any mailing lists that isn't advertised. - advertised = [] - listnames = Utils.list_names() - listnames.sort() - - for name in listnames: - mlist = MailList.MailList(name, lock=0) - if mlist.advertised: - if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ - mlist.web_page_url.find(hostname) == -1: - # List is for different identity of this host - skip it. - continue - else: - advertised.append((mlist.GetScriptURL('listinfo'), - mlist.real_name, - mlist.description)) - welcome = Header(1, _('General Information')).Format() - mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() - if not advertised: - welcome += Paragraph(_('There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.')).Format(css='class="strong"') - else: - welcome += Paragraph(_('''Below is a listing of all the public mailing lists on %(hostname)s. Click on a list name to get more information about the list, or to subscribe, unsubscribe, and change the preferences on your subscription.''')).Format() - - # set up some local variables - adj = msg and _('right') or '' - siteowner = Utils.get_site_email() - welcome += Paragraph( - _('''To visit the general information page for an unadvertised list, open a URL similar to this one, but with a '/' and the %(adj)s list name appended.''')).Format() - - welcome += Paragraph(_('''List administrators, you can visit ''') + - Link(Utils.ScriptURL('admin'), - _('the list admin overview page')).Format() + - _(''' to find the management interface for your list.''')).Format() - welcome += Paragraph(_('''If you are having trouble using the lists, please contact ''') + - Link('mailto:' + siteowner, siteowner).Format()).Format() - - if advertised: - highlight = 1 - for url, real_name, description in advertised: - table.AddRow( - [Link(url, real_name), - description or _('[no description available]')]) - if highlight: - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="title strong"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="title left"') - else: - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="strong"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') - highlight = not highlight - - doc.AddItem(welcome) - # When printing the mailing list table; avoid empty
      tag to appear when - # no mailing list / rows are present inside it. Empty
      tags are a violation - # in the "-//W3C//DTD XHTML 1.0 Transitional//EN" standard. - if advertised: - doc.AddItem(table) - doc.AddItem(MailmanLogo()) - print doc.Format() - - - -def list_listinfo(mlist, lang): - # Generate list specific listinfo - doc = HeadlessDocument() - doc.set_language(lang) - - replacements = mlist.GetStandardReplacements(lang) - - if not mlist.digestable or not mlist.nondigestable: - replacements[''] = "" - replacements[''] = "" - replacements[''] = '' - else: - replacements[''] = mlist.FormatDigestButton() - replacements[''] = \ - mlist.FormatUndigestButton() - replacements[''] = '' - replacements[''] = '' - replacements[''] = \ - mlist.FormatPlainDigestsButton() - replacements[''] = mlist.FormatMimeDigestsButton() - replacements[''] = mlist.FormatBox('email', size=30) - replacements[''] = mlist.FormatButton( - 'email-button', text=_('Subscribe')) - replacements[''] = mlist.FormatSecureBox('pw') - replacements[''] = mlist.FormatSecureBox('pw-conf') - replacements[''] = mlist.FormatFormStart( - 'subscribe') - # Roster form substitutions - replacements[''] = mlist.FormatFormStart('roster') - replacements[''] = mlist.FormatRosterOptionForUser(lang) - # Options form substitutions - replacements[''] = mlist.FormatFormStart('options') - replacements[''] = mlist.FormatEditingOption(lang) - replacements[''] = SubmitButton('UserOptions', - _('Edit Options')).Format() - # If only one language is enabled for this mailing list, omit the choice - # buttons. - if len(mlist.GetAvailableLanguages()) == 1: - displang = '' - else: - displang = mlist.FormatButton('displang-button', - text = _('View this page in')) - replacements[''] = displang - replacements[''] = mlist.FormatFormStart('listinfo') - replacements[''] = mlist.FormatBox('fullname', size=30) - - # Links on header section (errormsg) - listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() - listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() - replacements[''] = listinfo_link - replacements[''] = listadmin_link - replacements[''] = _('Mailing Lists') - - # Do the expansion. - doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang)) - print doc.Format() - - - -if __name__ == "__main__": - main() diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/options.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/options.py deleted file mode 100755 index 5e99064..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/options.py +++ /dev/null @@ -1,1023 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Produce and handle the member options.""" - -import sys -import os -import cgi -import signal -import urllib -from types import ListType - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MailList -from Mailman import Errors -from Mailman import MemberAdaptor -from Mailman import i18n -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -SLASH = '/' -SETLANGUAGE = -1 - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -def main(): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - parts = Utils.GetPathPieces() - lenparts = parts and len(parts) - if not parts or lenparts < 1: - title = _('CGI script error') - doc.SetTitle(title) - doc.addError(_('Invalid options to CGI script.')) - doc.AddItem(MailmanLogo()) - print doc.Format() - return - - # get the list and user's name - listname = parts[0].lower() - # open list - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - title = _('CGI script error') - doc.SetTitle(title) - doc.addError(_('No such list %(safelistname)s'), title) - doc.AddItem(MailmanLogo()) - print doc.Format() - syslog('error', 'No such list "%s": %s\n', listname, e) - return - - # The total contents of the user's response - cgidata = cgi.FieldStorage(keep_blank_values=1) - - # Set the language for the page. If we're coming from the listinfo cgi, - # we might have a 'language' key in the cgi data. That was an explicit - # preference to view the page in, so we should honor that here. If that's - # not available, use the list's default language. - language = cgidata.getvalue('language') - if not Utils.IsLanguage(language): - language = mlist.preferred_language - i18n.set_language(language) - doc.set_language(language) - - if lenparts < 2: - user = cgidata.getvalue('email') - if not user: - # If we're coming from the listinfo page and we left the email - # address field blank, it's not an error. listinfo.html names the - # button UserOptions; we can use that as the descriminator. - if not cgidata.getvalue('UserOptions'): - doc.addError(_('No address given')) - loginpage(mlist, doc, None, language) - print doc.Format() - return - else: - user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:]))) - - # Avoid cross-site scripting attacks - safeuser = Utils.websafe(user) - try: - Utils.ValidateEmail(user) - except Errors.EmailAddressError: - doc.addError(_('Illegal Email Address: %(safeuser)s')) - loginpage(mlist, doc, None, language) - print doc.Format() - return - # Sanity check the user, but only give the "no such member" error when - # using public rosters, otherwise, we'll leak membership information. - if not mlist.isMember(user) and mlist.private_roster == 0: - doc.addError(_('No such member: %(safeuser)s.')) - loginpage(mlist, doc, None, language) - print doc.Format() - return - - # Find the case preserved email address (the one the user subscribed with) - lcuser = user.lower() - try: - cpuser = mlist.getMemberCPAddress(lcuser) - except Errors.NotAMemberError: - # This happens if the user isn't a member but we've got private rosters - cpuser = None - if lcuser == cpuser: - cpuser = None - - # And now we know the user making the request, so set things up to for the - # user's stored preferred language, overridden by any form settings for - # their new language preference. - userlang = cgidata.getvalue('language') - if not Utils.IsLanguage(userlang): - userlang = mlist.getMemberLanguage(user) - doc.set_language(userlang) - i18n.set_language(userlang) - - # See if this is VARHELP on topics. - varhelp = None - if cgidata.has_key('VARHELP'): - varhelp = cgidata['VARHELP'].value - elif os.environ.get('QUERY_STRING'): - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') - if qs and type(qs) == types.ListType: - varhelp = qs[0] - if varhelp: - topic_details(mlist, doc, user, cpuser, userlang, varhelp) - return - - # Are we processing an unsubscription request from the login screen? - if cgidata.has_key('login-unsub'): - # Because they can't supply a password for unsubscribing, we'll need - # to do the confirmation dance. - if mlist.isMember(user): - # We must acquire the list lock in order to pend a request. - try: - mlist.Lock() - # If unsubs require admin approval, then this request has to - # be held. Otherwise, send a confirmation. - if mlist.unsubscribe_policy: - mlist.HoldUnsubscription(user) - doc.addMessage(_('Your unsubscription request has been forwarded to the list administrator for approval.'), - tag='', css='class="message warning strong"') - else: - mlist.ConfirmUnsubscription(user, userlang) - doc.addMessage(_('The confirmation email has been sent.'), - tag='', css='class="message warning strong"') - mlist.Save() - finally: - mlist.Unlock() - else: - # Not a member - if mlist.private_roster == 0: - # Public rosters - doc.addError(_('No such member: %(safeuser)s.')) - else: - syslog('mischief', - _('Unsub attempt of non-member w/ private rosters: %s'), - user) - doc.addMessage(_('The confirmation email has been sent.'), - tag='', css='class="message success strong"') - loginpage(mlist, doc, user, language) - print doc.Format() - return - - # Are we processing a password reminder from the login screen? - if cgidata.has_key('login-remind'): - if mlist.isMember(user): - mlist.MailUserPassword(user) - doc.addError( - _('A reminder of your password has been emailed to you.'), - tag='') - else: - # Not a member - if mlist.private_roster == 0: - # Public rosters - doc.addError(_('No such member: %(safeuser)s.')) - else: - syslog('mischief', - 'Reminder attempt of non-member w/ private rosters: %s', - user) - doc.addMessage( - _('A reminder of your password has been emailed to you.'), - tag='', css='class="message success strong"') - loginpage(mlist, doc, user, language) - print doc.Format() - return - - # Get the password from the form. - password = cgidata.getvalue('password', '').strip() - # Check authentication. We need to know if the credentials match the user - # or the site admin, because they are the only ones who are allowed to - # change things globally. Specifically, the list admin may not change - # values globally. - if mm_cfg.ALLOW_SITE_ADMIN_COOKIES: - user_or_siteadmin_context = (mm_cfg.AuthUser, mm_cfg.AuthSiteAdmin) - else: - # Site and list admins are treated equal so that list admin can pass - # site admin test. :-( - user_or_siteadmin_context = (mm_cfg.AuthUser,) - is_user_or_siteadmin = mlist.WebAuthenticate( - user_or_siteadmin_context, password, user) - # Authenticate, possibly using the password supplied in the login page - if not is_user_or_siteadmin and \ - not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - password, user): - # Not authenticated, so throw up the login page again. If they tried - # to authenticate via cgi (instead of cookie), then print an error - # message. - if cgidata.has_key('password'): - doc.addError(_('Authentication failed.')) - # So as not to allow membership leakage, prompt for the email - # address and the password here. - if mlist.private_roster <> 0: - syslog('mischief', - 'Login failure with private rosters: %s', - user) - user = None - loginpage(mlist, doc, user, language) - print doc.Format() - return - - # From here on out, the user is okay to view and modify their membership - # options. The first set of checks does not require the list to be - # locked. - - if cgidata.has_key('logout'): - print mlist.ZapCookie(mm_cfg.AuthUser, user) - loginpage(mlist, doc, user, language) - print doc.Format() - return - - if cgidata.has_key('emailpw'): - mlist.MailUserPassword(user) - msg = Div(Paragraph(_('A reminder of your password has been emailed to you.'))).Format(css='class="message success strong"') - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - - if cgidata.has_key('othersubs'): - # Only the user or site administrator can view all subscriptions. - if not is_user_or_siteadmin: - msg = Div(Paragraph(_('The list administrator may not view the other subscriptions for this user.'))).Format(css='class="message error strong"') - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - hostname = mlist.host_name - title = _('List of subscriptions for: %(safeuser)s') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - doc.AddItem(Paragraph(_('Click on a link to visit your options page for the requested mailing list.'))) - - # Troll through all the mailing lists that match host_name and see if - # the user is a member. If so, add it to the list. - onlists = [] - for gmlist in lists_of_member(mlist, user) + [mlist]: - url = gmlist.GetOptionsURL(user) - link = Link(url, gmlist.real_name) - onlists.append((gmlist.real_name, link)) - onlists.sort() - items = OrderedList(*[link for name, link in onlists]) - doc.AddItem(items) - doc.AddItem(MailmanLogo()) - print doc.Format() - return - - if cgidata.has_key('change-of-address'): - msg = '' - # We could be changing the user's full name, email address, or both. - # Watch out for non-ASCII characters in the member's name. - membername = cgidata.getvalue('fullname') - # Canonicalize the member's name - membername = Utils.canonstr(membername, language) - newaddr = cgidata.getvalue('new-address') - confirmaddr = cgidata.getvalue('confirm-address') - - oldname = mlist.getMemberName(user) - set_address = set_membername = 0 - - # See if the user wants to change their email address globally. The - # list admin is /not/ allowed to make global changes. - globally = cgidata.getvalue('changeaddr-globally') - if globally and not is_user_or_siteadmin: - msg += Div(Paragraph(_("The list administrator may not change the names or addresses for this user's other subscriptions. However, the subscription for this mailing list has been changed."))).Format(css='class="message warning strong"') - globally = False - # We will change the member's name under the following conditions: - # - membername has a value - # - membername has no value, but they /used/ to have a membername - if membername and membername <> oldname: - # Setting it to a new value - set_membername = 1 - if not membername and oldname: - # Unsetting it - set_membername = 1 - # We will change the user's address if both newaddr and confirmaddr - # are non-blank, have the same value, and aren't the currently - # subscribed email address (when compared case-sensitively). If both - # are blank, but membername is set, we ignore it, otherwise we print - # an error. - if newaddr and confirmaddr: - if newaddr <> confirmaddr: - msg += Div(Paragraph(_('Addresses did not match!'))).Format(css='class="message error strong"') - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - if newaddr == cpuser: - msg += Div(Paragraph(_('You are already using that email address'))).Format(css='class="message error strong"') - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - # If they're requesting to subscribe an address which is already a - # member, and they're /not/ doing it globally, then refuse. - # Otherwise, we'll agree to do it globally (with a warning - # message) and let ApprovedChangeMemberAddress() handle already a - # member issues. - if mlist.isMember(newaddr): - safenewaddr = Utils.websafe(newaddr) - if globally: - listname = mlist.real_name - msg += Div(Paragraph(_('The new address you requested %(newaddr)s is already a member of the %(listname)s mailing list, however you have also requested a global change of address. Upon confirmation, any other mailing list containing the address %(safeuser)s will be changed.'))).Format(css='class="message warning strong"') - # Don't return - else: - msg += Div(Paragraph( - _('The new address is already a member: %(newaddr)s') - )).Format(css='class="message error strong"') - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - set_address = 1 - elif (newaddr or confirmaddr) and not set_membername: - msg += Div(Paragraph(_('Addresses may not be blank'))).Format(css='class="message error strong"') - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - - # Standard sigterm handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - signal.signal(signal.SIGTERM, sigterm_handler) - if set_address: - if cpuser is None: - cpuser = user - # Register the pending change after the list is locked - msg = Div(Paragraph(_('A confirmation message has been sent to: %(newaddr)s.'))).Format(css='class="message success strong"') - mlist.Lock() - try: - try: - mlist.ChangeMemberAddress(cpuser, newaddr, globally) - mlist.Save() - finally: - mlist.Unlock() - except Errors.MMBadEmailError: - msg = Div(Paragraph(_('Bad email address provided'))).Format(css='class="message error strong"') - except Errors.MMHostileAddress: - msg = Div(Paragraph(_('Illegal email address provided'))).Format(css='class="message error strong"') - except Errors.MMAlreadyAMember: - msg = Div(Paragraph(_('%(newaddr)s is already a member of the list.'))).Format(css='class="message error strong"') - except Errors.MembershipIsBanned: - owneraddr = mlist.GetOwnerEmail() - msg = Div(Paragraph(_('%(newaddr)s is banned from this list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.'))).Format(css='class="message warning strong"') - - if set_membername: - mlist.Lock() - try: - mlist.ChangeMemberName(user, membername, globally) - mlist.Save() - finally: - mlist.Unlock() - msg += Div(Paragraph(_('Member name successfully changed.'))).Format(css='class="message success strong"') - - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - - if cgidata.has_key('changepw'): - newpw = cgidata.getvalue('newpw') - confirmpw = cgidata.getvalue('confpw') - if not newpw or not confirmpw: - options_page(mlist, doc, user, cpuser, userlang, - Div(Paragraph(_('Passwords may not be blank'))).Format(css='class="message error strong"')) - print doc.Format() - return - if newpw <> confirmpw: - options_page(mlist, doc, user, cpuser, userlang, - Div(Paragraph(_('Passwords did not match!'))).Format(css='class="message error strong"')) - print doc.Format() - return - - # See if the user wants to change their passwords globally, however - # the list admin is /not/ allowed to change passwords globally. - msg = '' - pw_globally = cgidata.getvalue('pw-globally') - if pw_globally and not is_user_or_siteadmin: - msg += Div(Paragraph(_('''The list administrator may not change the password for this user's other subscriptions. However, the password for this mailing list has been changed.'''))).Format(css='class="message warning strong"') - pw_globally = False - - mlists = [mlist] - - if pw_globally: - mlists.extend(lists_of_member(mlist, user)) - - for gmlist in mlists: - change_password(gmlist, user, newpw, confirmpw) - - # Regenerate the cookie so a re-authorization isn't necessary - print mlist.MakeCookie(mm_cfg.AuthUser, user) - msg += Div(Paragraph(_('Password successfully changed.'))).Format(css='class="message success strong"') - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - - if cgidata.has_key('unsub'): - # Was the confirming check box turned on? - if not cgidata.getvalue('unsubconfirm'): - options_page( - mlist, doc, user, cpuser, userlang, - Div(Paragraph(_('''You must confirm your unsubscription request by turning on the checkbox below the Unsubscribe button. You have not been unsubscribed!'''))).Format(css='class="message error strong"')) - print doc.Format() - return - - # Standard signal handler - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - # Okay, zap them. Leave them sitting at the list's listinfo page. We - # must own the list lock, and we want to make sure the user (BAW: and - # list admin?) is informed of the removal. - signal.signal(signal.SIGTERM, sigterm_handler) - mlist.Lock() - needapproval = False - try: - try: - mlist.DeleteMember( - user, _('via the member options page'), userack=1) - except Errors.MMNeedApproval: - needapproval = True - mlist.Save() - finally: - mlist.Unlock() - # Now throw up some results page, with appropriate links. We can't - # drop them back into their options page, because that's gone now! - fqdn_listname = mlist.GetListEmail() - owneraddr = mlist.GetOwnerEmail() - url = mlist.GetScriptURL('listinfo', absolute=1) - - title = _('Unsubscription results') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - if needapproval: - doc.AddItem(Paragraph(_('Your unsubscription request has been received and forwarded on to the list moderators for approval. You will receive notification once the list moderators have made their decision.'))) - else: - doc.AddItem(Paragraph(_('You have been successfully unsubscribed from the mailing list %(fqdn_listname)s. If you were receiving digest deliveries you may get one more digest. If you have any questions about your unsubscription, please contact the list owners at %(owneraddr)s.'))) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - if cgidata.has_key('options-submit'): - # Digest action flags - digestwarn = 0 - cantdigest = 0 - mustdigest = 0 - - newvals = [] - # First figure out which options have changed. The item names come - # from FormatOptionButton() in HTMLFormatter.py - for item, flag in (('digest', mm_cfg.Digests), - ('mime', mm_cfg.DisableMime), - ('dontreceive', mm_cfg.DontReceiveOwnPosts), - ('ackposts', mm_cfg.AcknowledgePosts), - ('disablemail', mm_cfg.DisableDelivery), - ('conceal', mm_cfg.ConcealSubscription), - ('remind', mm_cfg.SuppressPasswordReminder), - ('rcvtopic', mm_cfg.ReceiveNonmatchingTopics), - ('nodupes', mm_cfg.DontReceiveDuplicates), - ): - try: - newval = int(cgidata.getvalue(item)) - except (TypeError, ValueError): - newval = None - - # Skip this option if there was a problem or it wasn't changed. - # Note that delivery status is handled separate from the options - # flags. - if newval is None: - continue - elif flag == mm_cfg.DisableDelivery: - status = mlist.getDeliveryStatus(user) - # Here, newval == 0 means enable, newval == 1 means disable - if not newval and status <> MemberAdaptor.ENABLED: - newval = MemberAdaptor.ENABLED - elif newval and status == MemberAdaptor.ENABLED: - newval = MemberAdaptor.BYUSER - else: - continue - elif newval == mlist.getMemberOption(user, flag): - continue - # Should we warn about one more digest? - if flag == mm_cfg.Digests and \ - newval == 0 and mlist.getMemberOption(user, flag): - digestwarn = 1 - - newvals.append((flag, newval)) - - # The user language is handled a little differently - if userlang not in mlist.GetAvailableLanguages(): - newvals.append((SETLANGUAGE, mlist.preferred_language)) - else: - newvals.append((SETLANGUAGE, userlang)) - - # Process user selected topics, but don't make the changes to the - # MailList object; we must do that down below when the list is - # locked. - topicnames = cgidata.getvalue('usertopic') - if topicnames: - # Some topics were selected. topicnames can actually be a string - # or a list of strings depending on whether more than one topic - # was selected or not. - if not isinstance(topicnames, ListType): - # Assume it was a bare string, so listify it - topicnames = [topicnames] - # unquote the topic names - topicnames = [urllib.unquote_plus(n) for n in topicnames] - - # The standard sigterm handler (see above) - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - # Now, lock the list and perform the changes - mlist.Lock() - try: - signal.signal(signal.SIGTERM, sigterm_handler) - # `values' is a tuple of flags and the web values - for flag, newval in newvals: - # Handle language settings differently - if flag == SETLANGUAGE: - mlist.setMemberLanguage(user, newval) - # Handle delivery status separately - elif flag == mm_cfg.DisableDelivery: - mlist.setDeliveryStatus(user, newval) - else: - try: - mlist.setMemberOption(user, flag, newval) - except Errors.CantDigestError: - cantdigest = 1 - except Errors.MustDigestError: - mustdigest = 1 - # Set the topics information. - mlist.setMemberTopics(user, topicnames) - mlist.Save() - finally: - mlist.Unlock() - - # A bag of attributes for the global options - class Global: - enable = None - remind = None - nodupes = None - mime = None - def __nonzero__(self): - return len(self.__dict__.keys()) > 0 - - globalopts = Global() - - # The enable/disable option and the password remind option may have - # their global flags sets. - if cgidata.getvalue('deliver-globally'): - # Yes, this is inefficient, but the list is so small it shouldn't - # make much of a difference. - for flag, newval in newvals: - if flag == mm_cfg.DisableDelivery: - globalopts.enable = newval - break - - if cgidata.getvalue('remind-globally'): - for flag, newval in newvals: - if flag == mm_cfg.SuppressPasswordReminder: - globalopts.remind = newval - break - - if cgidata.getvalue('nodupes-globally'): - for flag, newval in newvals: - if flag == mm_cfg.DontReceiveDuplicates: - globalopts.nodupes = newval - break - - if cgidata.getvalue('mime-globally'): - for flag, newval in newvals: - if flag == mm_cfg.DisableMime: - globalopts.mime = newval - break - - # Change options globally, but only if this is the user or site admin, - # /not/ if this is the list admin. - if globalopts: - if not is_user_or_siteadmin: - doc.addMessage(_("""The list administrator may not change the options for this user's other subscriptions. However the options for this mailing list subscription has been changed."""), css='class="message warning strong"') - else: - for gmlist in lists_of_member(mlist, user): - global_options(gmlist, user, globalopts) - - # Now print the results - if cantdigest: - msg = _('''The list administrator has disabled digest delivery for this list, so your delivery option has not been set. However your other options have been set successfully.''') - elif mustdigest: - msg = _('''The list administrator has disabled non-digest delivery for this list, so your delivery option has not been set. However your other options have been set successfully.''') - else: - msg = _('You have successfully set your options.') - - if digestwarn: - msg += _(' You may get one last digest.') - - # Set message presentation - if cantdigest or mustdigest or digestwarn: - msg = Div(Paragraph(msg)).Format(css='class="message warning strong"') - else: - msg = Div(Paragraph(msg)).Format(css='class="message success strong"') - - options_page(mlist, doc, user, cpuser, userlang, msg) - print doc.Format() - return - - if mlist.isMember(user): - options_page(mlist, doc, user, cpuser, userlang) - else: - loginpage(mlist, doc, user, userlang) - print doc.Format() - - - -def options_page(mlist, doc, user, cpuser, userlang, message=''): - # The bulk of the document will come from the options.html template, which - # includes it's own html armor (head tags, etc.). Suppress the head that - # Document() derived pages get automatically. - doc.suppress_head = 1 - - if mlist.obscure_addresses: - presentable_user = Utils.ObscureEmail(user, for_text=1) - if cpuser is not None: - cpuser = Utils.ObscureEmail(cpuser, for_text=1) - else: - presentable_user = user - - fullname = Utils.uncanonstr(mlist.getMemberName(user), userlang) - if fullname: - presentable_user += ', %s' % Utils.websafe(fullname) - - # Do replacements - replacements = mlist.GetStandardReplacements(userlang) - if message: - # message should come formatted with - # Div(Paragraph()).Format(css='class="message success|error"') - replacements[''] = message - replacements[''] = mlist.FormatOptionButton( - mm_cfg.Digests, 1, user) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.Digests, 0, user) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.DisableMime, 1, user) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.DisableMime, 0, user) - replacements[''] = ( - CheckBox('mime-globally', 1, checked=0).Format()) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.DisableDelivery, 0, user) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.DisableDelivery, 1, user) - replacements[''] = mlist.FormatDisabledNotice(user) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.AcknowledgePosts, 0, user) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.AcknowledgePosts, 1, user) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.DontReceiveOwnPosts, 0, user) - replacements[''] = ( - mlist.FormatOptionButton(mm_cfg.DontReceiveOwnPosts, 1, user)) - replacements[''] = ( - mlist.FormatOptionButton(mm_cfg.SuppressPasswordReminder, 1, user)) - replacements[''] = ( - mlist.FormatOptionButton(mm_cfg.SuppressPasswordReminder, 0, user)) - replacements[''] = ( - mlist.FormatOptionButton(mm_cfg.ConcealSubscription, 0, user)) - replacements[''] = mlist.FormatOptionButton( - mm_cfg.ConcealSubscription, 1, user) - replacements[''] = ( - mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 1, user)) - replacements[''] = ( - mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 0, user)) - replacements[''] = ( - mlist.FormatButton('unsub', _('Unsubscribe')) + '

      ' + - CheckBox('unsubconfirm', 1, checked=0).Format() + - _('Yes, I really want to unsubscribe') + '

      ') - replacements[''] = mlist.FormatSecureBox('newpw') - replacements[''] = mlist.FormatSecureBox('confpw') - replacements[''] = ( - mlist.FormatButton('changepw', _("Change My Password"))) - replacements[''] = ( - mlist.FormatButton('othersubs', - _('List my other subscriptions'))) - replacements[''] = ( - mlist.FormatFormStart('options', user)) - replacements[''] = user - replacements[''] = presentable_user - replacements[''] = mlist.FormatButton( - 'emailpw', (_('Email My Password To Me'))) - replacements[''] = ( - mlist.FormatUmbrellaNotice(user, _("password"))) - replacements[''] = ( - mlist.FormatButton('logout', _('Log out'))) - replacements[''] = mlist.FormatButton( - 'options-submit', _('Submit My Changes')) - replacements[''] = ( - CheckBox('pw-globally', 1, checked=0).Format()) - replacements[''] = ( - CheckBox('deliver-globally', 1, checked=0).Format()) - replacements[''] = ( - CheckBox('remind-globally', 1, checked=0).Format()) - replacements[''] = ( - CheckBox('nodupes-globally', 1, checked=0).Format()) - - days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1)) - if days > 1: - units = _('days') - else: - units = _('day') - replacements[''] = _('%(days)d %(units)s') - - replacements[''] = mlist.FormatBox('new-address') - replacements[''] = mlist.FormatBox( - 'confirm-address') - replacements[''] = mlist.FormatButton( - 'change-of-address', _('Change My Address and Name')) - replacements[''] = CheckBox( - 'changeaddr-globally', 1, checked=0).Format() - replacements[''] = mlist.FormatBox( - 'fullname', value=fullname) - - # Create the topics radios. BAW: what if the list admin deletes a topic, - # but the user still wants to get that topic message? - usertopics = mlist.getMemberTopics(user) - if mlist.topics: - table = Table(border="0") - for name, pattern, description, emptyflag in mlist.topics: - quotedname = urllib.quote_plus(name) - details = Link(mlist.GetScriptURL('options') + - '/%s/?VARHELP=%s' % (user, quotedname), - ' (Details)') - if name in usertopics: - checked = 1 - else: - checked = 0 - table.AddRow([CheckBox('usertopic', quotedname, checked=checked), - name + details.Format()]) - topicsfield = table.Format() - else: - topicsfield = _('No topics defined') - replacements[''] = topicsfield - replacements[''] = ( - mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 0, user)) - replacements[''] = ( - mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 1, user)) - - if cpuser is not None: - replacements[''] = Div( - Paragraph(_('''You are subscribed to this list with the case-preserved address: %(cpuser)s.''') - )).Format(css='class="message warning strong"') - else: - replacements[''] = '' - - # Links on header section (errormsg) - # Beacause this is on a html template file replacements is need in - # order to make the exapantion of these links. - listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() - listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() - replacements[''] = listinfo_link - replacements[''] = listadmin_link - replacements[''] = _('Mailing Lists') - - doc.AddItem(mlist.ParseTags('options.html', replacements, userlang)) - - - -def loginpage(mlist, doc, user, lang): - realname = mlist.real_name - actionurl = mlist.GetScriptURL('options') - - # Set up the title. - title = _('%(realname)s: membership options') - doc.SetTitle(title) - doc.AddItem(Header(1, title)) - - # Set up what this page is for - if user is None: - title = _('Login page') - extra = _('email address and ') - else: - safeuser = Utils.websafe(user) - title = _('for user: ') + safeuser - obuser = Utils.ObscureEmail(user) - extra = '' - - doc.AddItem(Paragraph(title).Format(0, 'class="strong"')) - - # Language selecton box - # If only one language is enabled for this mailing list, omit the choice - # buttons. - if len(mlist.GetAvailableLanguages()) > 1: - langform = Form(actionurl) - langform.AddItem(Paragraph(SubmitButton('displang-button', - _('View this page in')).Format(), - mlist.GetLangSelectBox(lang)).Format()) - if user: - langform.AddItem(Paragraph(Hidden('email', user))) - doc.AddItem(langform) - - # Preamble - # Set up the login page - form = Form(actionurl) - table = Table() - doc.AddItem(Paragraph(_("""In order to change your membership option, you must first log in by giving your %(extra)s membership password in the section below. If you don't remember your membership password, you can have it emailed to you by clicking on the button below. If you just want to unsubscribe from this list, click on the Unsubscribe button and a confirmation message will be sent to you."""))) - - doc.AddItem(Paragraph(_('''Important: From this point on, you must have cookies enabled in your browser, otherwise none of your changes will take effect.'''))) - - # Password and login button - table.AddRow([Header(3, _('Login'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') - if user is None: - table.AddRow([_('Email address:'), - TextBox('email', size=20)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - else: - table.AddRow([Hidden('email', user)]) - table.AddRow([_('Password'), PasswordBox('password', size=20)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - table.AddRow(['', SubmitButton('login', _('Log in'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - # Unsubscribe section - table.AddRow([Header(3, _('Unsubscribe'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') - - table.AddRow([_("""By clicking on the Unsubscribe button, a confirmation message will be emailed to you. This message will have a link that you should click on to complete the removal process (you can also confirm by email; see the instructions in the confirmation message)."""), - SubmitButton('login-unsub', _('Unsubscribe'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - # Password reminder section - table.AddRow([Header(3, _('Password reminder'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') - - table.AddRow([_("""By clicking on the Remind button, your password will be emailed to you."""), - SubmitButton('login-remind', _('Remind'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - # Finish up glomming together the login page - form.AddItem(table) - doc.AddItem(form) - doc.AddItem(mlist.GetMailmanFooter()) - - - -def lists_of_member(mlist, user): - hostname = mlist.host_name - onlists = [] - for listname in Utils.list_names(): - # The current list will always handle things in the mainline - if listname == mlist.internal_name(): - continue - glist = MailList.MailList(listname, lock=0) - if glist.host_name <> hostname: - continue - if not glist.isMember(user): - continue - onlists.append(glist) - return onlists - - - -def change_password(mlist, user, newpw, confirmpw): - # This operation requires the list lock, so let's set up the signal - # handling so the list lock will get released when the user hits the - # browser stop button. - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - mlist.Unlock() - # ...and ensure we exit, otherwise race conditions could cause us to - # enter MailList.Save() while we're in the unlocked state, and that - # could be bad! - sys.exit(0) - - # Must own the list lock! - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) - # change the user's password. The password must already have been - # compared to the confirmpw and otherwise been vetted for - # acceptability. - mlist.setMemberPassword(user, newpw) - mlist.Save() - finally: - mlist.Unlock() - - - -def global_options(mlist, user, globalopts): - # Is there anything to do? - for attr in dir(globalopts): - if attr.startswith('_'): - continue - if getattr(globalopts, attr) is not None: - break - else: - return - - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - mlist.Unlock() - # ...and ensure we exit, otherwise race conditions could cause us to - # enter MailList.Save() while we're in the unlocked state, and that - # could be bad! - sys.exit(0) - - # Must own the list lock! - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) - - if globalopts.enable is not None: - mlist.setDeliveryStatus(user, globalopts.enable) - - if globalopts.remind is not None: - mlist.setMemberOption(user, mm_cfg.SuppressPasswordReminder, - globalopts.remind) - - if globalopts.nodupes is not None: - mlist.setMemberOption(user, mm_cfg.DontReceiveDuplicates, - globalopts.nodupes) - - if globalopts.mime is not None: - mlist.setMemberOption(user, mm_cfg.DisableMime, globalopts.mime) - - mlist.Save() - finally: - mlist.Unlock() - - - -def topic_details(mlist, doc, user, cpuser, userlang, varhelp): - # Find out which topic the user wants to get details of - reflist = varhelp.split('/') - name = None - topicname = _('') - if len(reflist) == 1: - topicname = urllib.unquote_plus(reflist[0]) - for name, pattern, description, emptyflag in mlist.topics: - if name == topicname: - break - else: - name = None - - if not name: - options_page(mlist, doc, user, cpuser, userlang, - _('Requested topic is not valid: %(topicname)s')) - print doc.Format() - return - - table = Table() - table.AddRow([Header(3,_('Topic filter details'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') - table.AddRow([_('Name:'), - Utils.websafe(name)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - table.AddRow([_('Pattern (as regexp):'), - '' + Utils.websafe(pattern) + '']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - table.AddRow([_('Description:'), - Utils.websafe(description)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - options_page(mlist, doc, user, cpuser, userlang, table.Format()) - print doc.Format() diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/private.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/private.py deleted file mode 100755 index e85a01d..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/private.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Provide a password-interface wrapper around private archives.""" - -import os -import sys -import cgi -import mimetypes - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MailList -from Mailman import Errors -from Mailman import i18n -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -# Set up i18n. Until we know which list is being requested, we use the -# server's default. -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - -SLASH = '/' - - - -def true_path(path): - "Ensure that the path is safe by removing .." - # Workaround for path traverse vulnerability. Unsuccessful attempts will - # be logged in logs/error. - parts = [x for x in path.split(SLASH) if x not in ('.', '..')] - return SLASH.join(parts)[1:] - - - -def guess_type(url, strict): - if hasattr(mimetypes, 'common_types'): - return mimetypes.guess_type(url, strict) - return mimetypes.guess_type(url) - - - -def main(): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - parts = Utils.GetPathPieces() - if not parts: - title = 'Private Archive Error' - doc.SetTitle(title) - doc.addError(_('You must specify a list.'), title) - doc.AddItem(MailmanLogo()) - print doc.Format() - return - - path = os.environ.get('PATH_INFO') - tpath = true_path(path) - if tpath <> path[1:]: - title = _('Private archive - "./" and "../" not allowed in URL.') - doc.SetTitle(title) - doc.addError(title) - doc.AddItem(MailmanLogo()) - print doc.Format() - syslog('mischief', 'Private archive hostile path: %s', path) - return - # BAW: This needs to be converted to the Site module abstraction - true_filename = os.path.join( - mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) - - listname = parts[0].lower() - mboxfile = '' - if len(parts) > 1: - mboxfile = parts[1] - - # See if it's the list's mbox file is being requested - if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \ - listname[:-5] == mboxfile[:-5]: - listname = listname[:-5] - else: - mboxfile = '' - - # If it's a directory, we have to append index.html in this script. We - # must also check for a gzipped file, because the text archives are - # usually stored in compressed form. - if os.path.isdir(true_filename): - true_filename = true_filename + '/index.html' - if not os.path.exists(true_filename) and \ - os.path.exists(true_filename + '.gz'): - true_filename = true_filename + '.gz' - - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - title = _('Private Archive Error') - doc.SetTitle(title) - doc.addError(_('No such list %(safelistname)s'), title) - doc.AddItem(MailmanLogo()) - print doc.Format() - syslog('error', 'No such list "%s": %s\n', listname, e) - return - - i18n.set_language(mlist.preferred_language) - doc.set_language(mlist.preferred_language) - - cgidata = cgi.FieldStorage() - username = cgidata.getvalue('username', '') - password = cgidata.getvalue('password', '') - - is_auth = 0 - realname = mlist.real_name - message = '' - - if not mlist.WebAuthenticate((mm_cfg.AuthUser, - mm_cfg.AuthListModerator, - mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - password, username): - if cgidata.has_key('submit'): - # This is a re-authorization attempt - message = Div(Paragraph(_('Authorization failed.'))).Format(css='class="message error strong"') - # Output the password form - charset = Utils.GetCharSet(mlist.preferred_language) - print 'Content-type: text/html; charset=' + charset + '\n\n' - # Put the original full path in the authorization form, but avoid - # trailing slash if we're not adding parts. We add it below. - action = mlist.GetScriptURL('private', absolute=1) - if parts[1:]: - action = os.path.join(action, SLASH.join(parts[1:])) - # If we added '/index.html' to true_filename, add a slash to the URL. - # We need this because we no longer add the trailing slash in the - # private.html template. It's always OK to test parts[-1] since we've - # already verified parts[0] is listname. The basic rule is if the - # post URL (action) is a directory, it must be slash terminated, but - # not if it's a file. Otherwise, relative links in the target archive - # page don't work. - if true_filename.endswith('/index.html') and parts[-1] <> 'index.html': - action += SLASH - # Escape web input parameter to avoid cross-site scripting. - print Utils.maketext( - 'private.html', - {'action' : Utils.websafe(action), - 'realname': mlist.real_name, - 'message' : message, - 'errormsg_header' : _('Mailing Lists'), - 'listinfo_link' : Link(Utils.ScriptURL('listinfo'), _('General Information')).Format(), - 'listadmin_link' : Link(Utils.ScriptURL('admin'), _('Administration')).Format(), - }, mlist=mlist) - return - - lang = mlist.getMemberLanguage(username) - i18n.set_language(lang) - doc.set_language(lang) - - # Authorization confirmed... output the desired file - try: - ctype, enc = guess_type(path, strict=0) - if ctype is None: - ctype = 'text/html' - if mboxfile: - f = open(os.path.join(mlist.archive_dir() + '.mbox', - mlist.internal_name() + '.mbox')) - ctype = 'text/plain' - elif true_filename.endswith('.gz'): - import gzip - f = gzip.open(true_filename, 'r') - else: - f = open(true_filename, 'r') - except IOError: - title = _('Private Archive Error') - doc.SetTitle(title) - doc.addError(_('Private archive file not found'), title) - doc.addItem(MailmanLogo()) - print doc.Format() - syslog('error', 'Private archive file not found: %s', true_filename) - else: - print 'Content-type: %s\n' % ctype - sys.stdout.write(f.read()) - f.close() diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/rmlist.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/rmlist.py deleted file mode 100755 index 415dff7..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/rmlist.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright (C) 2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Remove/delete mailing lists through the web. -""" - -import os -import cgi -import sys -import errno -import shutil - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MailList -from Mailman import Errors -from Mailman import i18n -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - - -def main(): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - cgidata = cgi.FieldStorage() - parts = Utils.GetPathPieces() - - if not parts: - # Bad URL specification - title = _('Bad URL specification') - doc.SetTitle(title) - doc.addMessage('The specified URL is not valid.', title, 'class="message error"') - doc.AddItem(MailmanLogo()) - print doc.Format() - syslog('error', 'Bad URL specification: %s', parts) - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - title = _('Nos such list') - doc.SetTitle(title) - doc.addMessage( - _('The %(safelistname)s mailing list does not exist.'), - title, - css='class="message error"') - doc.AddItem(MailmanLogo()) - print doc.Format() - syslog('error', 'No such list "%s": %s\n', listname, e) - return - - # Now that we have a valid mailing list, set the language - i18n.set_language(mlist.preferred_language) - doc.set_language(mlist.preferred_language) - - # Be sure the list owners are not sneaking around! - if not mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS: - title = _("You're being a sneaky list owner!") - doc.SetTitle(title) - doc.addMessage(title, css='class="message error strong"') - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - syslog('mischief', 'Attempt to sneakily delete a list: %s', listname) - return - - if cgidata.has_key('doit'): - process_request(doc, cgidata, mlist) - print doc.Format() - return - - request_deletion(doc, mlist) - - # Always add the footer and print the document - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def process_request(doc, cgidata, mlist): - password = cgidata.getvalue('password', '').strip() - try: - delarchives = int(cgidata.getvalue('delarchives', '0')) - except ValueError: - delarchives = 0 - - # Removing a list is limited to the list-creator (a.k.a. list-destroyer), - # the list-admin, or the site-admin. Don't use WebAuthenticate here - # because we want to be sure the actual typed password is valid, not some - # password sitting in a cookie. - if mlist.Authenticate((mm_cfg.AuthCreator, - mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - password) == mm_cfg.UnAuthorized: - request_deletion( - doc, mlist, - _('You are not authorized to delete this mailing list')) - # Add the footer to properly close the tags. - doc.AddItem(mlist.GetMailmanFooter()) - return - - # Do the MTA-specific list deletion tasks - if mm_cfg.MTA: - modname = 'Mailman.MTA.' + mm_cfg.MTA - __import__(modname) - sys.modules[modname].remove(mlist, cgi=1) - - REMOVABLES = ['lists/%s'] - - if delarchives: - REMOVABLES.extend(['archives/private/%s', - 'archives/private/%s.mbox', - 'archives/public/%s', - 'archives/public/%s.mbox', - ]) - - problems = 0 - listname = mlist.internal_name() - for dirtmpl in REMOVABLES: - dir = os.path.join(mm_cfg.VAR_PREFIX, dirtmpl % listname) - if os.path.islink(dir): - try: - os.unlink(dir) - except OSError, e: - if e.errno not in (errno.EACCES, errno.EPERM): raise - problems += 1 - syslog('error', - 'link %s not deleted due to permission problems', - dir) - elif os.path.isdir(dir): - try: - shutil.rmtree(dir) - except OSError, e: - if e.errno not in (errno.EACCES, errno.EPERM): raise - problems += 1 - syslog('error', - 'directory %s not deleted due to permission problems', - dir) - - title = _('Mailing list deletion results') - doc.SetTitle(title) - container = Container() - #container.AddItem(Header(1, title)) - if not problems: - container.addMessage(_('''You have successfully deleted the mailing list %(listname)s.'''), - css='class="message success strong"') - else: - sitelist = Utils.get_site_email(mlist.host_name) - container.AddItem(Paragraph(_('''There were some problems deleting the mailing list %(listname)s. Contact your site administrator at %(sitelist)s for details.'''))) - doc.AddItem(container) - doc.AddItem(Paragraph( - _('Return to the ') + - Link(Utils.ScriptURL('listinfo'), - _('general list overview')).Format() + - '
      ' + _('Return to the ') + - Link(Utils.ScriptURL('admin'), - _('administrative list overview')).Format())) - doc.AddItem(MailmanLogo()) - - - -def request_deletion(doc, mlist, errmsg=None): - realname = mlist.real_name - title = _('Permanently remove mailing list %(realname)s') - doc.SetTitle(title) - - container = Container() - - # Add any error message as first element in the page - if errmsg: - container.addMessage(errmsg, css='class="message error strong"') - - # Add header as second element in the page - container.AddItem(Header(1, title)) - - container.AddItem(Paragraph(_("""This page allows you as the list owner, to permanent remove this mailing list from the system. This action is not undoable so you should undertake it only if you are absolutely sure this mailing list has served its purpose and is no longer necessary."""))) - - container.AddItem(Paragraph(_('''Note that no warning will be sent to your list members and after this action, any subsequent messages sent to the mailing list, or any of its administrative addreses will bounce.'''))) - - container.AddItem(Paragraph(_('''You also have the option of removing the archives for this mailing list at this time. It is almost always recommended that you do not remove the archives, since they serve as the historical record of your mailing list.'''))) - - container.AddItem(Paragraph(_('''For your safety, you will be asked to reconfirm the list password.'''))) - - form = Form(mlist.GetScriptURL('rmlist')) - ftable = Table() - - ftable.AddRow([_('List password:'), PasswordBox('password')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, css='class="description"') - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, css='class="value"') - - ftable.AddRow([_('Also delete archives?'), - RadioButtonArray('delarchives', (_('No'), _('Yes')), - checked=0, values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, css='class="description"') - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, css='class="value"') - - ftable.AddRow([Link( - mlist.GetScriptURL('admin'), - _('Cancel and return to list administration')).Format()]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') - - ftable.AddRow([SubmitButton('doit', _('Delete this list'))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') - - form.AddItem(ftable) - container.AddItem(form) - doc.AddItem(container) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/roster.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/roster.py deleted file mode 100755 index 37937cc..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/roster.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (C) 1998-2003 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Produce subscriber roster, using listinfo form data, roster.html template. - -Takes listname in PATH_INFO. -""" - - -# We don't need to lock in this script, because we're never going to change -# data. - -import sys -import os -import cgi -import urllib - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MailList -from Mailman import Errors -from Mailman import i18n -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - - -def main(): - parts = Utils.GetPathPieces() - if not parts: - error_page(_('Invalid options to CGI script')) - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - error_page(_('No such list %(safelistname)s')) - syslog('error', 'roster: no such list "%s": %s', listname, e) - return - - cgidata = cgi.FieldStorage() - - # messages in form should go in selected language (if any...) - lang = cgidata.getvalue('language') - if not Utils.IsLanguage(lang): - lang = mlist.preferred_language - i18n.set_language(lang) - - # Perform authentication for protected rosters. If the roster isn't - # protected, then anybody can see the pages. If members-only or - # "admin"-only, then we try to cookie authenticate the user, and failing - # that, we check roster-email and roster-pw fields for a valid password. - # (also allowed: the list moderator, the list admin, and the site admin). - if mlist.private_roster == 0: - # No privacy - ok = 1 - elif mlist.private_roster == 1: - # Members only - addr = cgidata.getvalue('roster-email', '') - password = cgidata.getvalue('roster-pw', '') - ok = mlist.WebAuthenticate((mm_cfg.AuthUser, - mm_cfg.AuthListModerator, - mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - password, addr) - else: - # Admin only, so we can ignore the address field - password = cgidata.getvalue('roster-pw', '') - ok = mlist.WebAuthenticate((mm_cfg.AuthListModerator, - mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - password) - if not ok: - realname = mlist.real_name - doc = Document() - doc.set_language(lang) - error_page_doc(doc, _('%(realname)s roster authentication failed.')) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - # The document and its language - doc = HeadlessDocument() - doc.set_language(lang) - - replacements = mlist.GetAllReplacements(lang) - replacements[''] = mlist.FormatButton( - 'displang-button', - text = _('View this page in')) - replacements[''] = mlist.FormatFormStart('roster') - - # Links on header section (errormsg) - listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() - listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() - replacements[''] = listinfo_link - replacements[''] = listadmin_link - replacements[''] = _('Mailing Lists') - - doc.AddItem(mlist.ParseTags('roster.html', replacements, lang)) - print doc.Format() - - - -def error_page(errmsg): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - error_page_doc(doc, errmsg) - doc.AddItem(MailmanLogo()) - print doc.Format() - - -def error_page_doc(doc, errmsg, *args): - # Produce a simple error-message page on stdout and exit. - title = _('Error') - doc.SetTitle(title) - doc.addMessage(errmsg % args, title, css='class="message error"') diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/subscribe.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/subscribe.py deleted file mode 100755 index a69df34..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Cgi/subscribe.py +++ /dev/null @@ -1,270 +0,0 @@ -# Copyright (C) 1998-2003 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Process subscription or roster requests from listinfo form.""" - -import sys -import os -import cgi -import signal - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MailList -from Mailman import Errors -from Mailman import i18n -from Mailman import Message -from Mailman.UserDesc import UserDesc -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog - -SLASH = '/' -ERRORSEP = '\n\n

      ' - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - - -def main(): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - parts = Utils.GetPathPieces() - if not parts: - title = 'Error' - doc.SetTitle(title) - doc.addMessage(_('Invalid options to CGI script'), title, 'class="message error"') - doc.AddItem(MailmanLogo()) - print doc.Format() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - title = _('No such list') - doc.SetTitle(title) - doc.addMessage(_('The %(safelistname)s mailing list does not exist.'), - title, 'class="message error"') - doc.AddItem(MailmanLogo()) - print doc.Format() - syslog('error', 'No such list "%s": %s\n', listname, e) - return - - # See if the form data has a preferred language set, in which case, use it - # for the results. If not, use the list's preferred language. - cgidata = cgi.FieldStorage() - language = cgidata.getvalue('language') - if not Utils.IsLanguage(language): - language = mlist.preferred_language - i18n.set_language(language) - doc.set_language(language) - - # We need a signal handler to catch the SIGTERM that can come from Apache - # when the user hits the browser's STOP button. See the comment in - # admin.py for details. - # - # BAW: Strictly speaking, the list should not need to be locked just to - # read the request database. However the request database asserts that - # the list is locked in order to load it and it's not worth complicating - # that logic. - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - mlist.Unlock() - # ...and ensure we exit, otherwise race conditions could cause us to - # enter MailList.Save() while we're in the unlocked state, and that - # could be bad! - sys.exit(0) - - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) - - process_form(mlist, doc, cgidata, language) - mlist.Save() - finally: - mlist.Unlock() - - - -def process_form(mlist, doc, cgidata, lang): - listowner = mlist.GetOwnerEmail() - realname = mlist.real_name - results = [] - - # The email address being subscribed, required - email = cgidata.getvalue('email', '') - if not email: - results.append(Paragraph(_('You must supply a valid email address.')).Format()) - - fullname = cgidata.getvalue('fullname', '') - # Canonicalize the full name - fullname = Utils.canonstr(fullname, lang) - # Who was doing the subscribing? - remote = os.environ.get('REMOTE_HOST', - os.environ.get('REMOTE_ADDR', - 'unidentified origin')) - # Was an attempt made to subscribe the list to itself? - if email == mlist.GetListEmail(): - syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote) - results.append(Paragraph(_('You may not subscribe a list to itself!')).Format()) - # If the user did not supply a password, generate one for him - password = cgidata.getvalue('pw') - confirmed = cgidata.getvalue('pw-conf') - - if password is None and confirmed is None: - password = Utils.MakeRandomPassword() - elif password is None or confirmed is None: - results.append(Paragraph(_('If you supply a password, you must confirm it.')).Format()) - elif password <> confirmed: - results.append(Paragraph(_('Your passwords did not match.')).Format()) - - # Get the digest option for the subscription. - digestflag = cgidata.getvalue('digest') - if digestflag: - try: - digest = int(digestflag) - except ValueError: - digest = 0 - else: - digest = mlist.digest_is_default - - # Sanity check based on list configuration. BAW: It's actually bogus that - # the page allows you to set the digest flag if you don't really get the - # choice. :/ - if not mlist.digestable: - digest = 0 - elif not mlist.nondigestable: - digest = 1 - - if results: - print_results(mlist, ERRORSEP.join(results), doc, lang) - return - - # If this list has private rosters, we have to be careful about the - # message that gets printed, otherwise the subscription process can be - # used to mine for list members. It may be inefficient, but it's still - # possible, and that kind of defeats the purpose of private rosters. - # We'll use this string for all successful or unsuccessful subscription - # results. - if mlist.private_roster == 0: - # Public rosters - privacy_results = '' - else: - privacy_results = Paragraph(_("""Your subscription request has been received, and will soon be acted upon. Depending on the configuration of this mailing list, your subscription request may have to be first confirmed by you via email, or approved by the list moderator. If confirmation is required, you will soon get a confirmation email which contains further instructions.""")).Format() - - try: - userdesc = UserDesc(email, fullname, password, digest, lang) - mlist.AddMember(userdesc, remote) - results = '' - # Check for all the errors that mlist.AddMember can throw options on the - # web page for this cgi - except Errors.MembershipIsBanned: - results = Paragraph(_("""The email address you supplied is banned from this mailing list. If you think this restriction is erroneous, please contact the list owners at %(listowner)s.""")).Format() - except Errors.MMBadEmailError: - results = Paragraph(_("""The email address you supplied is not valid. (E.g. it must contain an `@'.)""")).Format() - except Errors.MMHostileAddress: - results = Paragraph(_('Your subscription is not allowed because the email address you gave is insecure.')).Format() - except Errors.MMSubscribeNeedsConfirmation: - # Results string depends on whether we have private rosters or not - if privacy_results: - results = privacy_results - else: - results = Paragraph(_("""Confirmation from your email address is required, to prevent anyone from subscribing you without permission. Instructions are being sent to you at %(email)s. Please note your subscription will not start until you confirm your subscription.""")).Format() - except Errors.MMNeedApproval, x: - # Results string depends on whether we have private rosters or not - if privacy_results: - results = privacy_results - else: - # We need to interpolate into x - x = _(x) - results = Paragraph(_("""Your subscription request was deferred because %(x)s. Your request has been forwarded to the list moderator. You will receive email informing you of the moderator's decision when they get to your request.""")).Format() - except Errors.MMAlreadyAMember: - # Results string depends on whether we have private rosters or not - if not privacy_results: - results = Paragraph(_('You are already subscribed.')).Format() - else: - results = privacy_results - # This could be a membership probe. For safety, let the user know - # a probe occurred. BAW: should we inform the list moderator? - listaddr = mlist.GetListEmail() - # Set the language for this email message to the member's language. - mlang = mlist.getMemberLanguage(email) - otrans = i18n.get_translation() - i18n.set_language(mlang) - try: - msg = Message.UserNotification( - mlist.getMemberCPAddress(email), - mlist.GetBouncesEmail(), - _('Mailman privacy alert'), - _("""\ -An attempt was made to subscribe your address to the mailing list -%(listaddr)s. You are already subscribed to this mailing list. - -Note that the list membership is not public, so it is possible that a bad -person was trying to probe the list for its membership. This would be a -privacy violation if we let them do this, but we didn't. - -If you submitted the subscription request and forgot that you were already -subscribed to the list, then you can ignore this message. If you suspect that -an attempt is being made to covertly discover whether you are a member of this -list, and you are worried about your privacy, then feel free to send a message -to the list administrator at %(listowner)s. -"""), lang=mlang) - finally: - i18n.set_translation(otrans) - msg.send(mlist) - # These shouldn't happen unless someone's tampering with the form - except Errors.MMCantDigestError: - results = Paragraph(_('This list does not support digest delivery.')).Format() - except Errors.MMMustDigestError: - results = Paragraph(_('This list only supports digest delivery.')).Format() - else: - # Everything's cool. Our return string actually depends on whether - # this list has private rosters or not - if privacy_results: - results = privacy_results - else: - results = Paragarph(_("""You have been successfully subscribed to the %(realname)s mailing list.""")).Format() - # Show the results - print_results(mlist, results, doc, lang) - - - -def print_results(mlist, results, doc, lang): - # The bulk of the document will come from the options.html template, which - # includes its own html armor (head tags, etc.). Suppress the head that - # Document() derived pages get automatically. - doc.suppress_head = 1 - - replacements = mlist.GetStandardReplacements(lang) - replacements[''] = results - - # Links on header section (errormsg) - listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() - listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() - replacements[''] = listinfo_link - replacements[''] = listadmin_link - replacements[''] = _('Mailing Lists') - - output = mlist.ParseTags('subscribe.html', replacements, lang) - doc.AddItem(output) - print doc.Format() diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Defaults.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Defaults.py deleted file mode 100644 index 34a56b6..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Defaults.py +++ /dev/null @@ -1,1372 +0,0 @@ -# -*- python -*- - -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Distributed default settings for significant Mailman config variables.""" - -# NEVER make site configuration changes to this file. ALWAYS make them in -# mm_cfg.py instead, in the designated area. See the comments in that file -# for details. - - -import os - -def seconds(s): return s -def minutes(m): return m * 60 -def hours(h): return h * 60 * 60 -def days(d): return d * 60 * 60 * 24 - -# Some convenient constants -try: - True, False -except NameError: - True = 1 - False = 0 - -Yes = yes = On = on = True -No = no = Off = off = False - - - -##### -# General system-wide defaults -##### - -# Should image logos be used? Set this to 0 to disable image logos from "our -# sponsors" and just use textual links instead (this will also disable the -# shortcut "favicon"). Otherwise, this should contain the URL base path to -# the logo images (and must contain the trailing slash).. If you want to -# disable Mailman's logo footer altogther, hack -# Mailman/htmlformat.py:MailmanLogo(), which also contains the hardcoded links -# and image names. -IMAGE_LOGOS = '/icons/' - -# The name of the Mailman favicon -SHORTCUT_ICON = 'mm-icon.png' - -# Don't change MAILMAN_URL, unless you want to point it at one of the mirrors. -MAILMAN_URL = 'http://www.gnu.org/software/mailman/index.html' -#MAILMAN_URL = 'http://www.list.org/' -#MAILMAN_URL = 'http://mailman.sf.net/' - -# Mailman needs to know about (at least) two fully-qualified domain names -# (fqdn); 1) the hostname used in your urls, and 2) the hostname used in email -# addresses for your domain. For example, if people visit your Mailman system -# with "http://www.dom.ain/mailman" then your url fqdn is "www.dom.ain", and -# if people send mail to your system via "yourlist@dom.ain" then your email -# fqdn is "dom.ain". DEFAULT_URL_HOST controls the former, and -# DEFAULT_EMAIL_HOST controls the latter. Mailman also needs to know how to -# map from one to the other (this is especially important if you're running -# with virtual domains). You use "add_virtualhost(urlfqdn, emailfqdn)" to add -# new mappings. -# -# If you don't need to change DEFAULT_EMAIL_HOST and DEFAULT_URL_HOST in your -# mm_cfg.py, then you're done; the default mapping is added automatically. If -# however you change either variable in your mm_cfg.py, then be sure to also -# include the following: -# -# add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST) -# -# because otherwise the default mappings won't be correct. -DEFAULT_EMAIL_HOST = 'localhost.localdomain' -DEFAULT_URL_HOST = 'localhost.localdomain' -DEFAULT_URL_PATTERN = 'http://%s/mailman/' - -# DEFAULT_HOST_NAME has been replaced with DEFAULT_EMAIL_HOST, however some -# sites may have the former in their mm_cfg.py files. If so, we'll believe -# that, otherwise we'll believe DEFAULT_EMAIL_HOST. Same for DEFAULT_URL. -DEFAULT_HOST_NAME = None -DEFAULT_URL = None - -HOME_PAGE = 'index.html' -MAILMAN_SITE_LIST = 'mailman' - -# Normally when a site administrator authenticates to a web page with the site -# password, they get a cookie which authorizes them as the list admin. It -# makes me nervous to hand out site auth cookies because if this cookie is -# cracked or intercepted, the intruder will have access to every list on the -# site. OTOH, it's dang handy to not have to re-authenticate to every list on -# the site. Set this value to Yes to allow site admin cookies. -ALLOW_SITE_ADMIN_COOKIES = No - -# Command that is used to convert text/html parts into plain text. This -# should output results to standard output. %(filename)s will contain the -# name of the temporary file that the program should operate on. -HTML_TO_PLAIN_TEXT_COMMAND = '/usr/bin/lynx -dump %(filename)s' - - - -##### -# Virtual domains -##### - -# Set up your virtual host mappings here. This is primarily used for the -# thru-the-web list creation, so its effects are currently fairly limited. -# Use add_virtualhost() call to add new mappings. The keys are strings as -# determined by Utils.get_domain(), the values are as appropriate for -# DEFAULT_HOST_NAME. -VIRTUAL_HOSTS = {} - -# When set to Yes, the listinfo and admin overviews of lists on the machine -# will be confined to only those lists whose web_page_url configuration option -# host is included within the URL by which the page is visited - only those -# "on the virtual host". When set to No, all advertised (i.e. public) lists -# are included in the overview. -VIRTUAL_HOST_OVERVIEW = On - - -# Helper function; use this in your mm_cfg.py files. If optional emailhost is -# omitted it defaults to urlhost with the first name stripped off, e.g. -# -# add_virtualhost('www.dom.ain') -# VIRTUAL_HOST['www.dom.ain'] -# ==> 'dom.ain' -# -def add_virtualhost(urlhost, emailhost=None): - DOT = '.' - if emailhost is None: - emailhost = DOT.join(urlhost.split(DOT)[1:]) - VIRTUAL_HOSTS[urlhost.lower()] = emailhost.lower() - -# And set the default -add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST) - -# Note that you will want to run bin/fix_url.py to change the domain of an -# existing list. bin/fix_url.py must be run within the bin/withlist script, -# like so: bin/withlist -l -r bin/fix_url.py - - - -##### -# Spam avoidance defaults -##### - -# This variable contains a list of 2-tuple of the format (header, regex) which -# the Mailman/Handlers/SpamDetect.py module uses to match against the current -# message. If the regex matches the given header in the current message, then -# it is flagged as spam. header is case-insensitive and should not include -# the trailing colon. regex is always matched with re.IGNORECASE. -# -# Note that the more searching done, the slower the whole process gets. Spam -# detection is run against all messages coming to either the list, or the -# -owners address, unless the message is explicitly approved. -KNOWN_SPAMMERS = [] - - - -##### -# Web UI defaults -##### - -# Almost all the colors used in Mailman's web interface are parameterized via -# the following variables. This lets you easily change the color schemes for -# your preferences without having to do major surgery on the source code. Note -# that in general, the template colors are not included here since it is easy -# enough to override the default template colors via site-wide, vdomain-wide, -# or list-wide specializations. -# -# This section is been moved to CSS (Common Style Sheets). -# - /error/includes/common.css -# - /error/includes/mailman.css -# -# The HTML output layout is been modified too in order to fit the W3C's XHTML -# 1.0 standard. You may use the following variables but they may be deprecated -# in the near feautre. -# -# For more information about the standards used, see: -# - http://www.w3.org/TR/xhtml1 -# - http://www.w3.org/TR/CSS21 - -WEB_BG_COLOR = 'white' # Page background -WEB_HEADER_COLOR = '#99ccff' # Major section headers -WEB_SUBHEADER_COLOR = '#fff0d0' # Minor section headers -WEB_ADMINITEM_COLOR = '#dddddd' # Option field background -WEB_ADMINPW_COLOR = '#99cccc' # Password box color -WEB_ERROR_COLOR = 'red' # Error message foreground -WEB_LINK_COLOR = '' # If true, forces LINK= -WEB_ALINK_COLOR = '' # If true, forces ALINK= -WEB_VLINK_COLOR = '' # If true, forces VLINK= -WEB_HIGHLIGHT_COLOR = '#dddddd' # If true, alternating rows - # in listinfo & admin display - - -##### -# Archive defaults -##### - -# The url template for the public archives. This will be used in several -# places, including the List-Archive: header, links to the archive on the -# list's listinfo page, and on the list's admin page. -# -# This should be a string with "%(listname)s" somewhere in it. Mailman will -# interpolate the name of the list into this. You can also include a -# "%(hostname)s" in the string, into which Mailman will interpolate -# the host name (usually DEFAULT_URL_HOST). -PUBLIC_ARCHIVE_URL = 'http://%(hostname)s/pipermail/%(listname)s' - -# Are archives on or off by default? -DEFAULT_ARCHIVE = On - -# Are archives public or private by default? -# 0=public, 1=private -DEFAULT_ARCHIVE_PRIVATE = 0 - -# ARCHIVE_TO_MBOX -#-1 - do not do any archiving -# 0 - do not archive to mbox, use builtin mailman html archiving only -# 1 - archive to mbox to use an external archiving mechanism only -# 2 - archive to both mbox and builtin mailman html archiving - -# use this to make both external archiving mechanism work and -# mailman's builtin html archiving. the flat mail file can be -# useful for searching, external archivers, etc. -ARCHIVE_TO_MBOX = 2 - -# 0 - yearly -# 1 - monthly -# 2 - quarterly -# 3 - weekly -# 4 - daily -DEFAULT_ARCHIVE_VOLUME_FREQUENCY = 1 -DEFAULT_DIGEST_VOLUME_FREQUENCY = 1 - -# These variables control the use of an external archiver. Normally if -# archiving is turned on (see ARCHIVE_TO_MBOX above and the list's archive* -# attributes) the internal Pipermail archiver is used. This is the default if -# both of these variables are set to No. When either is set, the value should -# be a shell command string which will get passed to os.popen(). This string -# can contain the following substitution strings: -# -# %(listname)s -- gets the internal name of the list -# %(hostname)s -- gets the email hostname for the list -# -# being archived will be substituted for this. Please note that os.popen() is -# used. -# -# Note that if you set one of these variables, you should set both of them -# (they can be the same string). This will mean your external archiver will -# be used regardless of whether public or private archives are selected. -PUBLIC_EXTERNAL_ARCHIVER = No -PRIVATE_EXTERNAL_ARCHIVER = No - -# A filter module that converts from multipart messages to "flat" messages -# (i.e. containing a single payload). This is required for Pipermail, and you -# may want to set it to 0 for external archivers. You can also replace it -# with your own module as long as it contains a process() function that takes -# a MailList object and a Message object. It should raise -# Errors.DiscardMessage if it wants to throw the message away. Otherwise it -# should modify the Message object as necessary. -ARCHIVE_SCRUBBER = 'Mailman.Handlers.Scrubber' - -# Control parameter whether Mailman.Handlers.Scrubber should use message -# attachment's filename as is indicated by the filename parameter or use -# 'attachement-xxx' instead. The default is set True because the applications -# on PC and Mac begin to use longer non-ascii filenames. Historically, it -# was set False in 2.1.6 for backward compatiblity but it was reset to True -# for safer operation in mailman-2.1.7. -SCRUBBER_DONT_USE_ATTACHMENT_FILENAME = True - -# Use of attachment filename extension per se is may be dangerous because -# virus fakes it. You can set this True if you filter the attachment by -# filename extension -SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION = False - -# This variable defines what happens to text/html subparts. They can be -# stripped completely, escaped, or filtered through an external program. The -# legal values are: -# 0 - Strip out text/html parts completely, leaving a notice of the removal in -# the message. If the outer part is text/html, the entire message is -# discarded. -# 1 - Remove any embedded text/html parts, leaving them as HTML-escaped -# attachments which can be separately viewed. Outer text/html parts are -# simply HTML-escaped. -# 2 - Leave it inline, but HTML-escape it -# 3 - Remove text/html as attachments but don't HTML-escape them. Note: this -# is very dangerous because it essentially means anybody can send an HTML -# email to your site containing evil JavaScript or web bugs, or other -# nasty things, and folks viewing your archives will be susceptible. You -# should only consider this option if you do heavy moderation of your list -# postings. -# -# Note: given the current archiving code, it is not possible to leave -# text/html parts inline and un-escaped. I wouldn't think it'd be a good idea -# to do anyway. -# -# The value can also be a string, in which case it is the name of a command to -# filter the HTML page through. The resulting output is left in an attachment -# or as the entirety of the message when the outer part is text/html. The -# format of the string must include a "%(filename)s" which will contain the -# name of the temporary file that the program should operate on. It should -# write the processed message to stdout. Set this to -# HTML_TO_PLAIN_TEXT_COMMAND to specify an HTML to plain text conversion -# program. -ARCHIVE_HTML_SANITIZER = 1 - -# Set this to Yes to enable gzipping of the downloadable archive .txt file. -# Note that this is /extremely/ inefficient, so an alternative is to just -# collect the messages in the associated .txt file and run a cron job every -# night to generate the txt.gz file. See cron/nightly_gzip for details. -GZIP_ARCHIVE_TXT_FILES = No - -# This sets the default `clobber date' policy for the archiver. When a -# message is to be archived either by Pipermail or an external archiver, -# Mailman can modify the Date: header to be the date the message was received -# instead of the Date: in the original message. This is useful if you -# typically receive messages with outrageous dates. Set this to 0 to retain -# the date of the original message, or to 1 to always clobber the date. Set -# it to 2 to perform `smart overrides' on the date; when the date is outside -# ARCHIVER_ALLOWABLE_SANE_DATE_SKEW (either too early or too late), then the -# received date is substituted instead. -ARCHIVER_CLOBBER_DATE_POLICY = 2 -ARCHIVER_ALLOWABLE_SANE_DATE_SKEW = days(15) - -# Pipermail archives contain the raw email addresses of the posting authors. -# Some view this as a goldmine for spam harvesters. Set this to Yes to -# moderately obscure email addresses, but note that this breaks mailto: URLs -# in the archives too. -ARCHIVER_OBSCURES_EMAILADDRS = Yes - -# Pipermail assumes that messages bodies contain US-ASCII text. -# Change this option to define a different character set to be used as -# the default character set for the archive. The term "character set" -# is used in MIME to refer to a method of converting a sequence of -# octets into a sequence of characters. If you change the default -# charset, you might need to add it to VERBATIM_ENCODING below. -DEFAULT_CHARSET = None - -# Most character set encodings require special HTML entity characters to be -# quoted, otherwise they won't look right in the Pipermail archives. However -# some character sets must not quote these characters so that they can be -# rendered properly in the browsers. The primary issue is multi-byte -# encodings where the octet 0x26 does not always represent the & character. -# This variable contains a list of such characters sets which are not -# HTML-quoted in the archives. -VERBATIM_ENCODING = ['iso-2022-jp'] - -# When the archive is public, should Mailman also make the raw Unix mbox file -# publically available? -PUBLIC_MBOX = No - - - -##### -# Delivery defaults -##### - -# Final delivery module for outgoing mail. This handler is used for message -# delivery to the list via the smtpd, and to an individual user. This value -# must be a string naming a module in the Mailman.Handlers package. -# -# WARNING: Sendmail has security holes and should be avoided. In fact, you -# must read the Mailman/Handlers/Sendmail.py file before it will work for -# you. -# -#DELIVERY_MODULE = 'Sendmail' -DELIVERY_MODULE = 'SMTPDirect' - -# MTA should name a module in Mailman/MTA which provides the MTA specific -# functionality for creating and removing lists. Some MTAs like Exim can be -# configured to automatically recognize new lists, in which case the MTA -# variable should be set to None. Use 'Manual' to print new aliases to -# standard out (or send an email to the site list owner) for manual twiddling -# of an /etc/aliases style file. Use 'Postfix' if you are using the Postfix -# MTA -- but then also see POSTFIX_STYLE_VIRTUAL_DOMAINS. -MTA = 'Manual' - -# If you set MTA='Postfix', then you also want to set the following variable, -# depending on whether you're using virtual domains in Postfix, and which -# style of virtual domain you're using. Set this flag to false if you're not -# using virtual domains in Postfix, or if you're using Sendmail-style virtual -# domains (where all addresses are visible in all domains). If you're using -# Postfix-style virtual domains, where aliases should only show up in the -# virtual domain, set this variable to the list of host_name values to write -# separate virtual entries for. I.e. if you run dom1.ain, dom2.ain, and -# dom3.ain, but only dom2 and dom3 are virtual, set this variable to the list -# ['dom2.ain', 'dom3.ain']. Matches are done against the host_name attribute -# of the mailing lists. See README.POSTFIX for details. -POSTFIX_STYLE_VIRTUAL_DOMAINS = [] - -# These variables describe the program to use for regenerating the aliases.db -# and virtual-mailman.db files, respectively, from the associated plain text -# files. The file being updated will be appended to this string (with a -# separating space), so it must be appropriate for os.system(). -POSTFIX_ALIAS_CMD = '/usr/sbin/postalias' -POSTFIX_MAP_CMD = '/usr/sbin/postmap' - -# Ceiling on the number of recipients that can be specified in a single SMTP -# transaction. Set to 0 to submit the entire recipient list in one -# transaction. Only used with the SMTPDirect DELIVERY_MODULE. -SMTP_MAX_RCPTS = 500 - -# Ceiling on the number of SMTP sessions to perform on a single socket -# connection. Some MTAs have limits. Set this to 0 to do as many as we like -# (i.e. your MTA has no limits). Set this to some number great than 0 and -# Mailman will close the SMTP connection and re-open it after this number of -# consecutive sessions. -SMTP_MAX_SESSIONS_PER_CONNECTION = 0 - -# Maximum number of simultaneous subthreads that will be used for SMTP -# delivery. After the recipients list is chunked according to SMTP_MAX_RCPTS, -# each chunk is handed off to the smptd by a separate such thread. If your -# Python interpreter was not built for threads, this feature is disabled. You -# can explicitly disable it in all cases by setting MAX_DELIVERY_THREADS to -# 0. This feature is only supported with the SMTPDirect DELIVERY_MODULE. -# -# NOTE: This is an experimental feature and limited testing shows that it may -# in fact degrade performance, possibly due to Python's global interpreter -# lock. Use with caution. -MAX_DELIVERY_THREADS = 0 - -# SMTP host and port, when DELIVERY_MODULE is 'SMTPDirect'. Make sure the -# host exists and is resolvable (i.e., if it's the default of "localhost" be -# sure there's a localhost entry in your /etc/hosts file!) -SMTPHOST = 'localhost' -SMTPPORT = 0 # default from smtplib - -# Command for direct command pipe delivery to sendmail compatible program, -# when DELIVERY_MODULE is 'Sendmail'. -SENDMAIL_CMD = '/usr/lib/sendmail' - -# Set these variables if you need to authenticate to your NNTP server for -# Usenet posting or reading. If no authentication is necessary, specify None -# for both variables. -NNTP_USERNAME = None -NNTP_PASSWORD = None - -# Set this if you have an NNTP server you prefer gatewayed lists to use. -DEFAULT_NNTP_HOST = '' - -# These variables controls how headers must be cleansed in order to be -# accepted by your NNTP server. Some servers like INN reject messages -# containing prohibited headers, or duplicate headers. The NNTP server may -# reject the message for other reasons, but there's little that can be -# programmatically done about that. See Mailman/Queue/NewsRunner.py -# -# First, these headers (case ignored) are removed from the original message. -NNTP_REMOVE_HEADERS = ['nntp-posting-host', 'nntp-posting-date', 'x-trace', - 'x-complaints-to', 'xref', 'date-received', 'posted', - 'posting-version', 'relay-version', 'received'] - -# Next, these headers are left alone, unless there are duplicates in the -# original message. Any second and subsequent headers are rewritten to the -# second named header (case preserved). -NNTP_REWRITE_DUPLICATE_HEADERS = [ - ('to', 'X-Original-To'), - ('cc', 'X-Original-Cc'), - ('content-transfer-encoding', 'X-Original-Content-Transfer-Encoding'), - ('mime-version', 'X-MIME-Version'), - ] - -# All `normal' messages which are delivered to the entire list membership go -# through this pipeline of handler modules. Lists themselves can override the -# global pipeline by defining a `pipeline' attribute. -GLOBAL_PIPELINE = [ - # These are the modules that do tasks common to all delivery paths. - 'SpamDetect', - 'Approve', - 'Replybot', - 'Moderate', - 'Hold', - 'MimeDel', - 'Scrubber', - 'Emergency', - 'Tagger', - 'CalcRecips', - 'AvoidDuplicates', - 'Cleanse', - 'CleanseDKIM', - 'CookHeaders', - # And now we send the message to the digest mbox file, and to the arch and - # news queues. Runners will provide further processing of the message, - # specific to those delivery paths. - 'ToDigest', - 'ToArchive', - 'ToUsenet', - # Now we'll do a few extra things specific to the member delivery - # (outgoing) path, finally leaving the message in the outgoing queue. - 'AfterDelivery', - 'Acknowledge', - 'ToOutgoing', - ] - -# This is the pipeline which messages sent to the -owner address go through -OWNER_PIPELINE = [ - 'SpamDetect', - 'Replybot', - 'CleanseDKIM', - 'OwnerRecips', - 'ToOutgoing', - ] - - -# This defines syslog() format strings for the SMTPDirect delivery module (see -# DELIVERY_MODULE above). Valid %()s string substitutions include: -# -# time -- the time in float seconds that it took to complete the smtp -# hand-off of the message from Mailman to your smtpd. -# -# size -- the size of the entire message, in bytes -# -# #recips -- the number of actual recipients for this message. -# -# #refused -- the number of smtp refused recipients (use this only in -# SMTP_LOG_REFUSED). -# -# listname -- the `internal' name of the mailing list for this posting -# -# msg_

      -- the value of the delivered message's given header. If -# the message had no such header, then "n/a" will be used. Note though -# that if the message had multiple such headers, then it is undefined -# which will be used. -# -# allmsg_
      - Same as msg_
      above, but if there are multiple -# such headers in the message, they will all be printed, separated by -# comma-space. -# -# sender -- the "sender" of the messages, which will be the From: or -# envelope-sender as determeined by the USE_ENVELOPE_SENDER variable -# below. -# -# The format of the entries is a 2-tuple with the first element naming the -# file in logs/ to print the message to, and the second being a format string -# appropriate for Python's %-style string interpolation. The file name is -# arbitrary; qfiles/ will be created automatically if it does not -# exist. - -# The format of the message printed for every delivered message, regardless of -# whether the delivery was successful or not. Set to None to disable the -# printing of this log message. -SMTP_LOG_EVERY_MESSAGE = ( - 'smtp', - '%(msg_message-id)s smtp to %(listname)s for %(#recips)d recips, completed in %(time).3f seconds') - -# This will only be printed if there were no immediate smtp failures. -# Mutually exclusive with SMTP_LOG_REFUSED. -SMTP_LOG_SUCCESS = ( - 'post', - 'post to %(listname)s from %(sender)s, size=%(size)d, message-id=%(msg_message-id)s, success') - -# This will only be printed if there were any addresses which encountered an -# immediate smtp failure. Mutually exclusive with SMTP_LOG_SUCCESS. -SMTP_LOG_REFUSED = ( - 'post', - 'post to %(listname)s from %(sender)s, size=%(size)d, message-id=%(msg_message-id)s, %(#refused)d failures') - -# This will be logged for each specific recipient failure. Additional %()s -# keys are: -# -# recipient -- the failing recipient address -# failcode -- the smtp failure code -# failmsg -- the actual smtp message, if available -SMTP_LOG_EACH_FAILURE = ( - 'smtp-failure', - 'delivery to %(recipient)s failed with code %(failcode)d: %(failmsg)s') - -# These variables control the format and frequency of VERP-like delivery for -# better bounce detection. VERP is Variable Envelope Return Path, defined -# here: -# -# http://cr.yp.to/proto/verp.txt -# -# This involves encoding the address of the recipient as we (Mailman) know it -# into the envelope sender address (i.e. the SMTP `MAIL FROM:' address). -# Thus, no matter what kind of forwarding the recipient has in place, should -# it eventually bounce, we will receive an unambiguous notice of the bouncing -# address. -# -# However, we're technically only "VERP-like" because we're doing the envelope -# sender encoding in Mailman, not in the MTA. We do require cooperation from -# the MTA, so you must be sure your MTA can be configured for extended address -# semantics. -# -# The first variable describes how to encode VERP envelopes. It must contain -# these three string interpolations: -# -# %(bounces)s -- the list-bounces mailbox will be set here -# %(mailbox)s -- the recipient's mailbox will be set here -# %(host)s -- the recipient's host name will be set here -# -# This example uses the default below. -# -# FQDN list address is: mylist@dom.ain -# Recipient is: aperson@a.nother.dom -# -# The envelope sender will be mylist-bounces+aperson=a.nother.dom@dom.ain -# -# Note that your MTA /must/ be configured to deliver such an addressed message -# to mylist-bounces! -VERP_FORMAT = '%(bounces)s+%(mailbox)s=%(host)s' - -# The second describes a regular expression to unambiguously decode such an -# address, which will be placed in the To: header of the bounce message by the -# bouncing MTA. Getting this right is critical -- and tricky. Learn your -# Python regular expressions. It must define exactly three named groups, -# bounces, mailbox and host, with the same definition as above. It will be -# compiled case-insensitively. -VERP_REGEXP = r'^(?P[^+]+?)\+(?P[^=]+)=(?P[^@]+)@.*$' - -# VERP format and regexp for probe messages -VERP_PROBE_FORMAT = '%(bounces)s+%(token)s' -VERP_PROBE_REGEXP = r'^(?P[^+]+?)\+(?P[^@]+)@.*$' -# Set this Yes to activate VERP probe for disabling by bounce -VERP_PROBES = No - -# A perfect opportunity for doing VERP is the password reminders, which are -# already addressed individually to each recipient. Set this to Yes to enable -# VERPs on all password reminders. -VERP_PASSWORD_REMINDERS = No - -# Another good opportunity is when regular delivery is personalized. Here -# again, we're already incurring the performance hit for addressing each -# individual recipient. Set this to Yes to enable VERPs on all personalized -# regular deliveries (personalized digests aren't supported yet). -VERP_PERSONALIZED_DELIVERIES = No - -# And finally, we can VERP normal, non-personalized deliveries. However, -# because it can be a significant performance hit, we allow you to decide how -# often to VERP regular deliveries. This is the interval, in number of -# messages, to do a VERP recipient address. The same variable controls both -# regular and digest deliveries. Set to 0 to disable occasional VERPs, set to -# 1 to VERP every delivery, or to some number > 1 for only occasional VERPs. -VERP_DELIVERY_INTERVAL = 0 - -# For nicer confirmation emails, use a VERP-like format which encodes the -# confirmation cookie in the reply address. This lets us put a more user -# friendly Subject: on the message, but requires cooperation from the MTA. -# Format is like VERP_FORMAT above, but with the following substitutions: -# -# %(addr)s -- the list-confirm mailbox will be set here -# %(cookie)s -- the confirmation cookie will be set here -VERP_CONFIRM_FORMAT = '%(addr)s+%(cookie)s' - -# This is analogous to VERP_REGEXP, but for splitting apart the -# VERP_CONFIRM_FORMAT. MUAs have been observed that mung -# From: local_part@host -# into -# To: "local_part" -# when replying, so we skip everything up to '<' if any. -VERP_CONFIRM_REGEXP = r'^(.*<)?(?P[^+]+?)\+(?P[^@]+)@.*$' - -# Set this to Yes to enable VERP-like (more user friendly) confirmations -VERP_CONFIRMATIONS = No - -# This is the maximum number of automatic responses sent to an address because -# of -request messages or posting hold messages. This limit prevents response -# loops between Mailman and misconfigured remote email robots. Mailman -# already inhibits automatic replies to any message labeled with a header -# "Precendence: bulk|list|junk". This is a fallback safety valve so it should -# be set fairly high. Set to 0 for no limit (probably useful only for -# debugging). -MAX_AUTORESPONSES_PER_DAY = 10 - - - -##### -# Qrunner defaults -##### - -# Which queues should the qrunner master watchdog spawn? This is a list of -# 2-tuples containing the name of the qrunner class (which must live in a -# module of the same name within the Mailman.Queue package), and the number of -# parallel processes to fork for each qrunner. If more than one process is -# used, each will take an equal subdivision of the hash space. - -# BAW: Eventually we may support weighted hash spaces. -# BAW: Although not enforced, the # of slices must be a power of 2 - -QRUNNERS = [ - ('ArchRunner', 1), # messages for the archiver - ('BounceRunner', 1), # for processing the qfile/bounces directory - ('CommandRunner', 1), # commands and bounces from the outside world - ('IncomingRunner', 1), # posts from the outside world - ('NewsRunner', 1), # outgoing messages to the nntpd - ('OutgoingRunner', 1), # outgoing messages to the smtpd - ('VirginRunner', 1), # internally crafted (virgin birth) messages - ('RetryRunner', 1), # retry temporarily failed deliveries - ] - -# Set this to Yes to use the `Maildir' delivery option. If you change this -# you will need to re-run bin/genaliases for MTAs that don't use list -# auto-detection. -# -# WARNING: If you want to use Maildir delivery, you /must/ start Mailman's -# qrunner as root, or you will get permission problems. -# -# NOTE: Maildir delivery is experimental for Mailman 2.1. -USE_MAILDIR = No -# NOTE: If you set USE_MAILDIR = Yes, add the following line to your mm_cfg.py -# file (uncommented of course!) -# QRUNNERS.append(('MaildirRunner', 1)) - -# After processing every file in the qrunner's slice, how long should the -# runner sleep for before checking the queue directory again for new files? -# This can be a fraction of a second, or zero to check immediately -# (essentially busy-loop as fast as possible). -QRUNNER_SLEEP_TIME = seconds(1) - -# When a message that is unparsable (by the email package) is received, what -# should we do with it? The most common cause of unparsable messages is -# broken MIME encapsulation, and the most common cause of that is viruses like -# Nimda. Set this variable to No to discard such messages, or to Yes to store -# them in qfiles/bad subdirectory. -QRUNNER_SAVE_BAD_MESSAGES = Yes - -# This flag causes Mailman to fsync() its data files after writing and -# flushing its contents. While this ensures the data is written to disk, -# avoiding data loss, it may be a performance killer. Note that this flag -# affects both message pickles and MailList config.pck files. -SYNC_AFTER_WRITE = No - - - -##### -# General defaults -##### - -# The default language for this server. Whenever we can't figure out the list -# context or user context, we'll fall back to using this language. See -# LC_DESCRIPTIONS below for legal values. -DEFAULT_SERVER_LANGUAGE = 'en' - -# When allowing only members to post to a mailing list, how is the sender of -# the message determined? If this variable is set to Yes, then first the -# message's envelope sender is used, with a fallback to the sender if there is -# no envelope sender. Set this variable to No to always use the sender. -# -# The envelope sender is set by the SMTP delivery and is thus less easily -# spoofed than the sender, which is typically just taken from the From: header -# and thus easily spoofed by the end-user. However, sometimes the envelope -# sender isn't set correctly and this will manifest itself by postings being -# held for approval even if they appear to come from a list member. If you -# are having this problem, set this variable to No, but understand that some -# spoofed messages may get through. -USE_ENVELOPE_SENDER = No - -# Membership tests for posting purposes are usually performed by looking at a -# set of headers, passing the test if any of their values match a member of -# the list. Headers are checked in the order given in this variable. The -# value None means use the From_ (envelope sender) header. Field names are -# case insensitive. -SENDER_HEADERS = ('from', None, 'reply-to', 'sender') - -# How many members to display at a time on the admin cgi to unsubscribe them -# or change their options? -DEFAULT_ADMIN_MEMBER_CHUNKSIZE = 30 - -# how many bytes of a held message post should be displayed in the admindb web -# page? Use a negative number to indicate the entire message, regardless of -# size (though this will slow down rendering those pages). -ADMINDB_PAGE_TEXT_LIMIT = 4096 - -# Set this variable to Yes to allow list owners to delete their own mailing -# lists. You may not want to give them this power, in which case, setting -# this variable to No instead requires list removal to be done by the site -# administrator, via the command line script bin/rmlist. -OWNERS_CAN_DELETE_THEIR_OWN_LISTS = No - -# Set this variable to Yes to allow list owners to set the "personalized" -# flags on their mailing lists. Turning these on tells Mailman to send -# separate email messages to each user instead of batching them together for -# delivery to the MTA. This gives each member a more personalized message, -# but can have a heavy impact on the performance of your system. -OWNERS_CAN_ENABLE_PERSONALIZATION = No - -# Should held messages be saved on disk as Python pickles or as plain text? -# The former is more efficient since we don't need to go through the -# parse/generate roundtrip each time, but the latter might be preferred if you -# want to edit the held message on disk. -HOLD_MESSAGES_AS_PICKLES = Yes - -# This variable controls the order in which list-specific category options are -# presented in the admin cgi page. -ADMIN_CATEGORIES = [ - # First column - 'general', 'passwords', 'language', 'members', 'nondigest', 'digest', - # Second column - 'privacy', 'bounce', 'archive', 'gateway', 'autoreply', - 'contentfilter', 'topics', - ] - -# See "Bitfield for user options" below; make this a sum of those options, to -# make all new members of lists start with those options flagged. We assume -# by default that people don't want to receive two copies of posts. Note -# however that the member moderation flag's initial value is controlled by the -# list's config variable default_member_moderation. -DEFAULT_NEW_MEMBER_OPTIONS = 256 - -# Specify the type of passwords to use, when Mailman generates the passwords -# itself, as would be the case for membership requests where the user did not -# fill in a password, or during list creation, when auto-generation of admin -# passwords was selected. -# -# Set this value to Yes for classic Mailman user-friendly(er) passwords. -# These generate semi-pronounceable passwords which are easier to remember. -# Set this value to No to use more cryptographically secure, but harder to -# remember, passwords -- if your operating system and Python version support -# the necessary feature (specifically that /dev/urandom be available). -USER_FRIENDLY_PASSWORDS = Yes -# This value specifies the default lengths of member and list admin passwords -MEMBER_PASSWORD_LENGTH = 10 -ADMIN_PASSWORD_LENGTH = 10 - - - -##### -# List defaults. NOTE: Changing these values does NOT change the -# configuration of an existing list. It only defines the default for new -# lists you subsequently create. -##### - -# Should a list, by default be advertised? What is the default maximum number -# of explicit recipients allowed? What is the default maximum message size -# allowed? -DEFAULT_LIST_ADVERTISED = Yes -DEFAULT_MAX_NUM_RECIPIENTS = 10 -DEFAULT_MAX_MESSAGE_SIZE = 40 # KB - -# These format strings will be expanded w.r.t. the dictionary for the -# mailing list instance. -DEFAULT_SUBJECT_PREFIX = "[%(real_name)s] " -# DEFAULT_SUBJECT_PREFIX = "[%(real_name)s %%d]" # for numbering -DEFAULT_MSG_HEADER = "" -DEFAULT_MSG_FOOTER = """_______________________________________________ -%(real_name)s mailing list -%(real_name)s@%(host_name)s -%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s -""" - -# Where to put subject prefix for 'Re:' messages: -# -# old style: Re: [prefix] test -# new style: [prefix 123] Re: test ... (number is optional) -# -# Old style is default for backward compatibility. New style is forced if a -# list owner set %d (numbering) in prefix. If the site owner had applied new -# style patch (from SF patch area) before, he/she may want to set this No in -# mm_cfg.py. -OLD_STYLE_PREFIXING = Yes - -# Scrub regular delivery -DEFAULT_SCRUB_NONDIGEST = False - -# Mail command processor will ignore mail command lines after designated max. -DEFAULT_MAIL_COMMANDS_MAX_LINES = 25 - -# Is the list owner notified of admin requests immediately by mail, as well as -# by daily pending-request reminder? -DEFAULT_ADMIN_IMMED_NOTIFY = Yes - -# Is the list owner notified of subscribes/unsubscribes? -DEFAULT_ADMIN_NOTIFY_MCHANGES = No - -# Discard held messages after this days -DEFAULT_MAX_DAYS_TO_HOLD = 0 - -# Should list members, by default, have their posts be moderated? -DEFAULT_DEFAULT_MEMBER_MODERATION = No - -# Should non-member posts which are auto-discarded also be forwarded to the -# moderators? -DEFAULT_FORWARD_AUTO_DISCARDS = Yes - -# What shold happen to non-member posts which are do not match explicit -# non-member actions? -# 0 = Accept -# 1 = Hold -# 2 = Reject -# 3 = Discard -DEFAULT_GENERIC_NONMEMBER_ACTION = 1 - -# Bounce if 'To:', 'Cc:', or 'Resent-To:' fields don't explicitly name list? -# This is an anti-spam measure -DEFAULT_REQUIRE_EXPLICIT_DESTINATION = Yes - -# Alternate names acceptable as explicit destinations for this list. -DEFAULT_ACCEPTABLE_ALIASES =""" -""" -# For mailing lists that have only other mailing lists for members: -DEFAULT_UMBRELLA_LIST = No - -# For umbrella lists, the suffix for the account part of address for -# administrative notices (subscription confirmations, password reminders): -DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX = "-owner" - -# This variable controls whether monthly password reminders are sent. -DEFAULT_SEND_REMINDERS = Yes - -# Send welcome messages to new users? -DEFAULT_SEND_WELCOME_MSG = Yes - -# Send goodbye messages to unsubscribed members? -DEFAULT_SEND_GOODBYE_MSG = Yes - -# Wipe sender information, and make it look like the list-admin -# address sends all messages -DEFAULT_ANONYMOUS_LIST = No - -# {header-name: regexp} spam filtering - we include some for example sake. -DEFAULT_BOUNCE_MATCHING_HEADERS = """ -# Lines that *start* with a '#' are comments. -to: friend@public.com -message-id: relay.comanche.denmark.eu -from: list@listme.com -from: .*@uplinkpro.com -""" - -# Mailman can be configured to "munge" Reply-To: headers for any passing -# messages. One the one hand, there are a lot of good reasons not to munge -# Reply-To: but on the other, people really seem to want this feature. See -# the help for reply_goes_to_list in the web UI for links discussing the -# issue. -# 0 - Reply-To: not munged -# 1 - Reply-To: set back to the list -# 2 - Reply-To: set to an explicit value (reply_to_address) -DEFAULT_REPLY_GOES_TO_LIST = 0 - -# Mailman can be configured to strip any existing Reply-To: header, or simply -# extend any existing Reply-To: with one based on the above setting. -DEFAULT_FIRST_STRIP_REPLY_TO = No - -# SUBSCRIBE POLICY -# 0 - open list (only when ALLOW_OPEN_SUBSCRIBE is set to 1) ** -# 1 - confirmation required for subscribes -# 2 - admin approval required for subscribes -# 3 - both confirmation and admin approval required -# -# ** please do not choose option 0 if you are not allowing open -# subscribes (next variable) -DEFAULT_SUBSCRIBE_POLICY = 1 - -# Does this site allow completely unchecked subscriptions? -ALLOW_OPEN_SUBSCRIBE = No - -# The default policy for unsubscriptions. 0 (unmoderated unsubscribes) is -# highly recommended! -# 0 - unmoderated unsubscribes -# 1 - unsubscribes require approval -DEFAULT_UNSUBSCRIBE_POLICY = 0 - -# Private_roster == 0: anyone can see, 1: members only, 2: admin only. -DEFAULT_PRIVATE_ROSTER = 1 - -# When exposing members, make them unrecognizable as email addrs, so -# web-spiders can't pick up addrs for spam purposes. -DEFAULT_OBSCURE_ADDRESSES = Yes - -# RFC 2369 defines List-* headers which are added to every message sent -# through to the mailing list membership. These are a very useful aid to end -# users and should always be added. However, not all MUAs are compliant and -# if a list's membership has many such users, they may clamor for these -# headers to be suppressed. By setting this variable to Yes, list owners will -# be given the option to suppress these headers. By setting it to No, list -# owners will not be given the option to suppress these headers (although some -# header suppression may still take place, i.e. for announce-only lists, or -# lists with no archives). -ALLOW_RFC2369_OVERRIDES = Yes - -# Defaults for content filtering on mailing lists. DEFAULT_FILTER_CONTENT is -# a flag which if set to true, turns on content filtering. -DEFAULT_FILTER_CONTENT = No - -# DEFAULT_FILTER_MIME_TYPES is a list of MIME types to be removed. This is a -# list of strings of the format "maintype/subtype" or simply "maintype". -# E.g. "text/html" strips all html attachments while "image" strips all image -# types regardless of subtype (jpeg, gif, etc.). -DEFAULT_FILTER_MIME_TYPES = [] - -# DEFAULT_PASS_MIME_TYPES is a list of MIME types to be passed through. -# Format is the same as DEFAULT_FILTER_MIME_TYPES -DEFAULT_PASS_MIME_TYPES = ['multipart/mixed', - 'multipart/alternative', - 'text/plain'] - -# DEFAULT_FILTER_FILENAME_EXTENSIONS is a list of filename extensions to be -# removed. It is useful because many viruses fake their content-type as -# harmless ones while keep their extension as executable and expect to be -# executed when victims 'open' them. -DEFAULT_FILTER_FILENAME_EXTENSIONS = [ - 'exe', 'bat', 'cmd', 'com', 'pif', 'scr', 'vbs', 'cpl' - ] - -# DEFAULT_PASS_FILENAME_EXTENSIONS is a list of filename extensions to be -# passed through. Format is the same as DEFAULT_FILTER_FILENAME_EXTENSIONS. -DEFAULT_PASS_FILENAME_EXTENSIONS = [] - -# Replace multipart/alternative with its first alternative. -DEFAULT_COLLAPSE_ALTERNATIVES = Yes - -# Whether text/html should be converted to text/plain after content filtering -# is performed. Conversion is done according to HTML_TO_PLAIN_TEXT_COMMAND -DEFAULT_CONVERT_HTML_TO_PLAINTEXT = Yes - -# Default action to take on filtered messages. -# 0 = Discard, 1 = Reject, 2 = Forward, 3 = Preserve -DEFAULT_FILTER_ACTION = 0 - -# Whether to allow list owners to preserve content filtered messages to a -# special queue on the disk. -OWNERS_CAN_PRESERVE_FILTERED_MESSAGES = Yes - -# Check for administrivia in messages sent to the main list? -DEFAULT_ADMINISTRIVIA = Yes - - - -##### -# Digestification defaults. Same caveat applies here as with list defaults. -##### - -# Will list be available in non-digested form? -DEFAULT_NONDIGESTABLE = Yes - -# Will list be available in digested form? -DEFAULT_DIGESTABLE = Yes -DEFAULT_DIGEST_HEADER = "" -DEFAULT_DIGEST_FOOTER = DEFAULT_MSG_FOOTER - -DEFAULT_DIGEST_IS_DEFAULT = No -DEFAULT_MIME_IS_DEFAULT_DIGEST = No -DEFAULT_DIGEST_SIZE_THRESHHOLD = 30 # KB -DEFAULT_DIGEST_SEND_PERIODIC = Yes - -# Headers which should be kept in both RFC 1153 (plain) and MIME digests. RFC -# 1153 also specifies these headers in this exact order, so order matters. -MIME_DIGEST_KEEP_HEADERS = [ - 'Date', 'From', 'To', 'Cc', 'Subject', 'Message-ID', 'Keywords', - # I believe we should also keep these headers though. - 'In-Reply-To', 'References', 'Content-Type', 'MIME-Version', - 'Content-Transfer-Encoding', 'Precedence', 'Reply-To', - # Mailman 2.0 adds these headers - 'Message', - ] - -PLAIN_DIGEST_KEEP_HEADERS = [ - 'Message', 'Date', 'From', - 'Subject', 'To', 'Cc', - 'Message-ID', 'Keywords', - 'Content-Type', - ] - - - -##### -# Bounce processing defaults. Same caveat applies here as with list defaults. -##### - -# Should we do any bounced mail response at all? -DEFAULT_BOUNCE_PROCESSING = Yes - -# How often should the bounce qrunner process queued detected bounces? -REGISTER_BOUNCES_EVERY = minutes(15) - -# Bounce processing works like this: when a bounce from a member is received, -# we look up the `bounce info' for this member. If there is no bounce info, -# this is the first bounce we've received from this member. In that case, we -# record today's date, and initialize the bounce score (see below for initial -# value). -# -# If there is existing bounce info for this member, we look at the last bounce -# receive date. If this date is farther away from today than the `bounce -# expiration interval', we throw away all the old data and initialize the -# bounce score as if this were the first bounce from the member. -# -# Otherwise, we increment the bounce score. If we can determine whether the -# bounce was soft or hard (i.e. transient or fatal), then we use a score value -# of 0.5 for soft bounces and 1.0 for hard bounces. Note that we only score -# one bounce per day. If the bounce score is then greater than the `bounce -# threshold' we disable the member's address. -# -# After disabling the address, we can send warning messages to the member, -# providing a confirmation cookie/url for them to use to re-enable their -# delivery. After a configurable period of time, we'll delete the address. -# When we delete the address due to bouncing, we'll send one last message to -# the member. - -# Bounce scores greater than this value get disabled. -DEFAULT_BOUNCE_SCORE_THRESHOLD = 5.0 - -# Bounce information older than this interval is considered stale, and is -# discarded. -DEFAULT_BOUNCE_INFO_STALE_AFTER = days(7) - -# The number of notifications to send to the disabled/removed member before we -# remove them from the list. A value of 0 means we remove the address -# immediately (with one last notification). Note that the first one is sent -# upon change of status to disabled. -DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS = 3 - -# The interval of time between disabled warnings. -DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL = days(7) - -# Does the list owner get messages to the -bounces (and -admin) address that -# failed to match by the bounce detector? -DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER = Yes - -# Notifications on bounce actions. The first specifies whether the list owner -# should get a notification when a member is disabled due to bouncing, while -# the second specifies whether the owner should get one when the member is -# removed due to bouncing. -DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE = Yes -DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL = Yes - - - -##### -# General time limits -##### - -# Default length of time a pending request is live before it is evicted from -# the pending database. -PENDING_REQUEST_LIFE = days(3) - -# How long should messages which have delivery failures continue to be -# retried? After this period of time, a message that has failed recipients -# will be dequeued and those recipients will never receive the message. -DELIVERY_RETRY_PERIOD = days(5) - -# How long should we wait before we retry a temporary delivery failure? -DELIVERY_RETRY_WAIT = hours(1) - - - -##### -# Lock management defaults -##### - -# These variables control certain aspects of lock acquisition and retention. -# They should be tuned as appropriate for your environment. All variables are -# specified in units of floating point seconds. YOU MAY NEED TO TUNE THESE -# VARIABLES DEPENDING ON THE SIZE OF YOUR LISTS, THE PERFORMANCE OF YOUR -# HARDWARE, NETWORK AND GENERAL MAIL HANDLING CAPABILITIES, ETC. - -# Set this to On to turn on MailList object lock debugging messages, which -# will be written to logs/locks. If you think you're having lock problems, or -# just want to tune the locks for your system, turn on lock debugging. -LIST_LOCK_DEBUGGING = Off - -# This variable specifies how long the lock will be retained for a specific -# operation on a mailing list. Watch your logs/lock file and if you see a lot -# of lock breakages, you might need to bump this up. However if you set this -# too high, a faulty script (or incorrect use of bin/withlist) can prevent the -# list from being used until the lifetime expires. This is probably one of -# the most crucial tuning variables in the system. -LIST_LOCK_LIFETIME = hours(5) - -# This variable specifies how long an attempt will be made to acquire a list -# lock by the incoming qrunner process. If the lock acquisition times out, -# the message will be re-queued for later delivery. -LIST_LOCK_TIMEOUT = seconds(10) - -# Set this to On to turn on lock debugging messages for the pending requests -# database, which will be written to logs/locks. If you think you're having -# lock problems, or just want to tune the locks for your system, turn on lock -# debugging. -PENDINGDB_LOCK_DEBUGGING = Off - - - -##### -# Nothing below here is user configurable. Most of these values are in this -# file for internal system convenience. Don't change any of them or override -# any of them in your mm_cfg.py file! -##### - -# These directories are used to find various important files in the Mailman -# installation. PREFIX and EXEC_PREFIX are set by configure and should point -# to the installation directory of the Mailman package. -PYTHON = '/usr/bin/python' -PREFIX = '/usr/lib/mailman' -EXEC_PREFIX = '${prefix}' -VAR_PREFIX = '/var/lib/mailman' - -# Work around a bogus autoconf 2.12 bug -if EXEC_PREFIX == '${prefix}': - EXEC_PREFIX = PREFIX - -# CGI extension, change using configure script -CGIEXT = '' - -# Group id that group-owns the Mailman installation -MAILMAN_USER = 'mailman' -MAILMAN_GROUP = 'mailman' - -# Enumeration for Mailman cgi widget types -Toggle = 1 -Radio = 2 -String = 3 -Text = 4 -Email = 5 -EmailList = 6 -Host = 7 -Number = 8 -FileUpload = 9 -Select = 10 -Topics = 11 -Checkbox = 12 -# An "extended email list". Contents must be an email address or a ^-prefixed -# regular expression. Used in the sender moderation text boxes. -EmailListEx = 13 -# Extended spam filter widget -HeaderFilter = 14 - -# Actions -DEFER = 0 -APPROVE = 1 -REJECT = 2 -DISCARD = 3 -SUBSCRIBE = 4 -UNSUBSCRIBE = 5 -ACCEPT = 6 -HOLD = 7 - -# Standard text field width -TEXTFIELDWIDTH = 40 - -# Bitfield for user options. See DEFAULT_NEW_MEMBER_OPTIONS above to set -# defaults for all new lists. -Digests = 0 # handled by other mechanism, doesn't need a flag. -DisableDelivery = 1 # Obsolete; use set/getDeliveryStatus() -DontReceiveOwnPosts = 2 # Non-digesters only -AcknowledgePosts = 4 -DisableMime = 8 # Digesters only -ConcealSubscription = 16 -SuppressPasswordReminder = 32 -ReceiveNonmatchingTopics = 64 -Moderate = 128 -DontReceiveDuplicates = 256 - -# A mapping between short option tags and their flag -OPTINFO = {'hide' : ConcealSubscription, - 'nomail' : DisableDelivery, - 'ack' : AcknowledgePosts, - 'notmetoo': DontReceiveOwnPosts, - 'digest' : 0, - 'plain' : DisableMime, - 'nodupes' : DontReceiveDuplicates - } - -# Authentication contexts. -# -# Mailman defines the following roles: - -# - User, a normal user who has no permissions except to change their personal -# option settings -# - List creator, someone who can create and delete lists, but cannot -# (necessarily) configure the list. -# - List moderator, someone who can tend to pending requests such as -# subscription requests, or held messages -# - List administrator, someone who has total control over a list, can -# configure it, modify user options for members of the list, subscribe and -# unsubscribe members, etc. -# - Site administrator, someone who has total control over the entire site and -# can do any of the tasks mentioned above. This person usually also has -# command line access. - -UnAuthorized = 0 -AuthUser = 1 # Joe Shmoe User -AuthCreator = 2 # List Creator / Destroyer -AuthListAdmin = 3 # List Administrator (total control over list) -AuthListModerator = 4 # List Moderator (can only handle held requests) -AuthSiteAdmin = 5 # Site Administrator (total control over everything) - -# Useful directories -LIST_DATA_DIR = os.path.join(VAR_PREFIX, 'lists') -LOG_DIR = '/var/log/mailman' -LOCK_DIR = '/var/lock/mailman' -CONFIG_DIR = '/etc/mailman' -DATA_DIR = os.path.join(VAR_PREFIX, 'data') -PID_DIR = '/var/run/mailman' -SPAM_DIR = os.path.join(VAR_PREFIX, 'spam') -WRAPPER_DIR = os.path.join(EXEC_PREFIX, 'mail') -BIN_DIR = os.path.join(PREFIX, 'bin') -SCRIPTS_DIR = os.path.join(PREFIX, 'scripts') -TEMPLATE_DIR = os.path.join(PREFIX, 'templates') -MESSAGES_DIR = os.path.join(PREFIX, 'messages') -PUBLIC_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX, 'archives', 'public') -PRIVATE_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX, 'archives', 'private') - -# Directories used by the qrunner subsystem -QUEUE_DIR = '/var/spool/mailman' -INQUEUE_DIR = os.path.join(QUEUE_DIR, 'in') -OUTQUEUE_DIR = os.path.join(QUEUE_DIR, 'out') -CMDQUEUE_DIR = os.path.join(QUEUE_DIR, 'commands') -BOUNCEQUEUE_DIR = os.path.join(QUEUE_DIR, 'bounces') -NEWSQUEUE_DIR = os.path.join(QUEUE_DIR, 'news') -ARCHQUEUE_DIR = os.path.join(QUEUE_DIR, 'archive') -SHUNTQUEUE_DIR = os.path.join(QUEUE_DIR, 'shunt') -VIRGINQUEUE_DIR = os.path.join(QUEUE_DIR, 'virgin') -BADQUEUE_DIR = os.path.join(QUEUE_DIR, 'bad') -RETRYQUEUE_DIR = os.path.join(QUEUE_DIR, 'retry') -MAILDIR_DIR = os.path.join(QUEUE_DIR, 'maildir') - -# Other useful files -PIDFILE = os.path.join(PID_DIR, 'master-qrunner.pid') -SITE_PW_FILE = os.path.join(CONFIG_DIR, 'adm.pw') -LISTCREATOR_PW_FILE = os.path.join(CONFIG_DIR, 'creator.pw') - -# Import a bunch of version numbers -from Version import * - -# Vgg: Language descriptions and charsets dictionary, any new supported -# language must have a corresponding entry here. Key is the name of the -# directories that hold the localized texts. Data are tuples with first -# element being the description, as described in the catalogs, and second -# element is the language charset. I have chosen code from /usr/share/locale -# in my GNU/Linux. :-) -def _(s): - return s - -LC_DESCRIPTIONS = {} - -def add_language(code, description, charset): - LC_DESCRIPTIONS[code] = (description, charset) - -add_language('ar', _('Arabic'), 'utf-8') -add_language('ca', _('Catalan'), 'iso-8859-1') -add_language('cs', _('Czech'), 'iso-8859-2') -add_language('da', _('Danish'), 'iso-8859-1') -add_language('de', _('German'), 'iso-8859-1') -add_language('en', _('English (USA)'), 'us-ascii') -add_language('es', _('Spanish (Spain)'), 'iso-8859-1') -add_language('et', _('Estonian'), 'iso-8859-15') -add_language('eu', _('Euskara'), 'iso-8859-15') # Basque -add_language('fi', _('Finnish'), 'iso-8859-1') -add_language('fr', _('French'), 'iso-8859-1') -add_language('hr', _('Croatian'), 'iso-8859-2') -add_language('hu', _('Hungarian'), 'iso-8859-2') -add_language('ia', _('Interlingua'), 'iso-8859-15') -add_language('it', _('Italian'), 'iso-8859-1') -add_language('ja', _('Japanese'), 'euc-jp') -add_language('ko', _('Korean'), 'euc-kr') -add_language('lt', _('Lithuanian'), 'iso-8859-13') -add_language('nl', _('Dutch'), 'iso-8859-1') -add_language('no', _('Norwegian'), 'iso-8859-1') -add_language('pl', _('Polish'), 'iso-8859-2') -add_language('pt', _('Portuguese'), 'iso-8859-1') -add_language('pt_BR', _('Portuguese (Brazil)'), 'iso-8859-1') -add_language('ro', _('Romanian'), 'iso-8859-2') -add_language('ru', _('Russian'), 'koi8-r') -add_language('sr', _('Serbian'), 'utf-8') -add_language('sl', _('Slovenian'), 'iso-8859-2') -add_language('sv', _('Swedish'), 'iso-8859-1') -add_language('tr', _('Turkish'), 'iso-8859-9') -add_language('uk', _('Ukrainian'), 'utf-8') -add_language('vi', _('Vietnamese'), 'utf-8') -add_language('zh_CN', _('Chinese (China)'), 'utf-8') -add_language('zh_TW', _('Chinese (Taiwan)'), 'utf-8') - -del _ diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Archive.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Archive.py deleted file mode 100644 index a3de3fc..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Archive.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -from Mailman import mm_cfg -from Mailman.i18n import _ -from Mailman.Gui.GUIBase import GUIBase - - - -class Archive(GUIBase): - def GetConfigCategory(self): - return 'archive', _('Archiving') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'archive': - return None - return [ - '

      ' + _("List traffic archival policies.") + '

      ', - - ('archive', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('Archive messages?')), - - ('archive_private', mm_cfg.Radio, (_('Public'), _('Private')), 0, - _('Is archive file source for public or private archival?')), - - ('archive_volume_frequency', mm_cfg.Radio, - (_('Yearly'), _('Monthly'), _('Quarterly'), - _('Weekly'), _('Daily')), - 0, - _('How often should a new archive volume be started?')), - ] diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Autoresponse.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Autoresponse.py deleted file mode 100644 index 9fb9679..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Autoresponse.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright (C) 2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Administrative GUI for the autoresponder.""" - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.i18n import _ -from Mailman.Gui.GUIBase import GUIBase - -# These are the allowable string substitution variables -ALLOWEDS = ('listname', 'listurl', 'requestemail', 'adminemail', 'owneremail') - - - -class Autoresponse(GUIBase): - def GetConfigCategory(self): - return 'autoreply', _('Auto-responder') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'autoreply': - return None - WIDTH = mm_cfg.TEXTFIELDWIDTH - - return [ - '

      ' + _('Auto-responder characteristics.') + '

      ' + - '

      ' + _('In the text fields below, string interpolation is performed with the following key/value substitutions:') + - '

      ' + - '''
        -
      • listname - ''' + _('gets the name of the mailing list') + '''
      • -
      • listurl - ''' + _("gets the list's listinfo URL") + '''
      • -
      • requestemail - ''' + _("gets the list's -request address") + '''
      • -
      • owneremail - ''' + _("gets the list's -owner address") + '''
      • -
      - -

      ''' + _('For each text field, you can either enter the text directly into the text box, or you can specify a file on your local system to upload as the text.') + '''

      ''', - - ('autorespond_postings', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('''Should Mailman send an auto-response to mailing list - posters?''')), - - ('autoresponse_postings_text', mm_cfg.FileUpload, - (6, WIDTH), 0, - _('Auto-response text to send to mailing list posters.')), - - ('autorespond_admin', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('''Should Mailman send an auto-response to emails sent to the - -owner address?''')), - - ('autoresponse_admin_text', mm_cfg.FileUpload, - (6, WIDTH), 0, - _('Auto-response text to send to -owner emails.')), - - ('autorespond_requests', mm_cfg.Radio, - (_('No'), _('Yes, w/discard'), _('Yes, w/forward')), 0, - _('''Should Mailman send an auto-response to emails sent to the - -request address? If you choose yes, decide whether you want - Mailman to discard the original email, or forward it on to the - system as a normal mail command.''')), - - ('autoresponse_request_text', mm_cfg.FileUpload, - (6, WIDTH), 0, - _('Auto-response text to send to -request emails.')), - - ('autoresponse_graceperiod', mm_cfg.Number, 3, 0, - _('''Number of days between auto-responses to either the mailing - list or -request/-owner address from the same poster. Set to - zero (or negative) for no grace period (i.e. auto-respond to - every message).''')), - ] - - def _setValue(self, mlist, property, val, doc): - # Handle these specially because we may need to convert to/from - # external $-string representation. - if property in ('autoresponse_postings_text', - 'autoresponse_admin_text', - 'autoresponse_request_text'): - val = self._convertString(mlist, property, ALLOWEDS, val, doc) - if val is None: - # There was a problem, so don't set it - return - GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Bounce.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Bounce.py deleted file mode 100644 index 943241a..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Bounce.py +++ /dev/null @@ -1,165 +0,0 @@ -# Copyright (C) 2001-2004 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -from Mailman import mm_cfg -from Mailman.i18n import _ -from Mailman.mm_cfg import days -from Mailman.Gui.GUIBase import GUIBase - - - -class Bounce(GUIBase): - def GetConfigCategory(self): - return 'bounce', _('Bounce processing') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'bounce': - return None - return [ - '\n

      ' + - _("""These policies control the automatic bounce processing system in Mailman. Here's an overview of how it works.""") + - '\n

      ' + - '\n

      ' + - _('When a bounce is received, Mailman tries to extract two pieces of information from the message: the address of the member the message was intended for, and the severity of the problem causing the bounce. The severity can be either hard or soft meaning either a fatal error occurred, or a transient error occurred. When in doubt, a hard severity is used.') + - '\n

      ' + - '\n

      ' + - _('If no member address can be extracted from the bounce, then the bounce is usually discarded. Otherwise, each member is assigned a bounce score and every time we encounter a bounce from this member we increment the score. Hard bounces increment by 1 while soft bounces increment by 0.5. We only increment the bounce score once per day, so even if we receive ten hard bounces from a member per day, their score will increase by only 1 for that day.') + - '\n

      ' + - '\n

      ' + - _("""When a member's bounce score is greater than the bounce score threshold, the subscription is disabled. Once disabled, the member will not receive any postings from the list until their membership is explicitly re-enabled (either by the list administrator or the user). However, they will receive occasional reminders that their membership has been disabled, and these reminders will include information about how to re-enable their membership.""") + - '\n

      ' + - '\n

      ' + - _("""You can control both the number of reminders the member will receive and the frequency with which these reminders are sent.""") + - '\n

      ' + - '\n

      ' + - _('There is one other important configuration variable; after a certain period of time -- during which no bounces from the member are received -- the bounce information is considered stale and discarded. Thus by adjusting this value, and the score threshold, you can control how quickly bouncing members are disabled. You should tune both of these to the frequency and traffic volume of your list.') + - '\n

      ', - - _('Bounce detection sensitivity'), - - ('bounce_processing', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('Should Mailman perform automatic bounce processing?'), - _("""By setting this value to No, you disable all - automatic bounce processing for this list, however bounce - messages will still be discarded so that the list administrator - isn't inundated with them.""")), - - ('bounce_score_threshold', mm_cfg.Number, 5, 0, - _("""The maximum member bounce score before the member's - subscription is disabled. This value can be a floating point - number."""), - _("""Each subscriber is assigned a bounce score, as a floating - point number. Whenever Mailman receives a bounce from a list - member, that member's score is incremented. Hard bounces (fatal - errors) increase the score by 1, while soft bounces (temporary - errors) increase the score by 0.5. Only one bounce per day - counts against a member's score, so even if 10 bounces are - received for a member on the same day, their score will increase - by just 1.

      - -

      This variable describes the upper limit for a member's bounce - score, above which they are automatically disabled, but not - removed from the mailing list.""")), - - ('bounce_info_stale_after', mm_cfg.Number, 5, 0, - _("""The number of days after which a member's bounce information - is discarded, if no new bounces have been received in the - interim. This value must be an integer.""")), - - ('bounce_you_are_disabled_warnings', mm_cfg.Number, 5, 0, - _("""How many Your Membership Is Disabled warnings a - disabled member should get before their address is removed from - the mailing list. Set to 0 to immediately remove an address from - the list once their bounce score exceeds the threshold. This - value must be an integer.""")), - - ('bounce_you_are_disabled_warnings_interval', mm_cfg.Number, 5, 0, - _("""The number of days between sending the Your Membership - Is Disabled warnings. This value must be an integer.""")), - - _('Notifications'), - - ('bounce_unrecognized_goes_to_list_owner', mm_cfg.Toggle, - (_('No'), _('Yes')), 0, - _('''Should Mailman send you, the list owner, any bounce messages - that failed to be detected by the bounce processor? Yes - is recommended.'''), - _("""While Mailman's bounce detector is fairly robust, it's - impossible to detect every bounce format in the world. You - should keep this variable set to Yes for two reasons: 1) - If this really is a permanent bounce from one of your members, - you should probably manually remove them from your list, and 2) - you might want to send the message on to the Mailman developers - so that this new format can be added to its known set.

      - -

      If you really can't be bothered, then set this variable to - No and all non-detected bounces will be discarded - without further processing.

      - -

      Note: This setting will also affect all messages sent - to your list's -admin address. This address is deprecated and - should never be used, but some people may still send mail to this - address. If this happens, and this variable is set to - No those messages too will get discarded. You may want - to set up an - autoresponse - message for email to the -owner and -admin address.""")), - - ('bounce_notify_owner_on_disable', mm_cfg.Toggle, - (_('No'), _('Yes')), 0, - _("""Should Mailman notify you, the list owner, when bounces - cause a member's subscription to be disabled?"""), - _("""By setting this value to No, you turn off notification messages that are normally sent to the list owners when a member's delivery is disabled due to excessive bounces. An attempt to notify the member will always be made.""")), - - ('bounce_notify_owner_on_removal', mm_cfg.Toggle, - (_('No'), _('Yes')), 0, - _("""Should Mailman notify you, the list owner, when bounces - cause a member to be unsubscribed?"""), - _("""By setting this value to No, you turn off notification messages that are normally sent to the list owners when a member is unsubscribed due to excessive bounces. An attempt to notify the member will always be made.""")), - - ] - - def _setValue(self, mlist, property, val, doc): - # Do value conversion from web representation to internal - # representation. - try: - if property == 'bounce_processing': - val = int(val) - elif property == 'bounce_score_threshold': - val = float(val) - elif property == 'bounce_info_stale_after': - val = days(int(val)) - elif property == 'bounce_you_are_disabled_warnings': - val = int(val) - elif property == 'bounce_you_are_disabled_warnings_interval': - val = days(int(val)) - elif property == 'bounce_notify_owner_on_disable': - val = int(val) - elif property == 'bounce_notify_owner_on_removal': - val = int(val) - except ValueError: - doc.addError( - _("""Bad value for %(property)s: %(val)s"""), - tag = _('Error: ')) - return - GUIBase._setValue(self, mlist, property, val, doc) - - def getValue(self, mlist, kind, varname, params): - if varname not in ('bounce_info_stale_after', - 'bounce_you_are_disabled_warnings_interval'): - return None - return int(getattr(mlist, varname) / days(1)) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/ContentFilter.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/ContentFilter.py deleted file mode 100644 index 83e5c15..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/ContentFilter.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright (C) 2002-2005 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""GUI component managing the content filtering options.""" - -from Mailman import mm_cfg -from Mailman.i18n import _ -from Mailman.Gui.GUIBase import GUIBase - -NL = '\n' - - - -class ContentFilter(GUIBase): - def GetConfigCategory(self): - return 'contentfilter', _('Content filtering') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'contentfilter': - return None - WIDTH = mm_cfg.TEXTFIELDWIDTH - - actions = [_('Discard'), _('Reject'), _('Forward to List Owner')] - if mm_cfg.OWNERS_CAN_PRESERVE_FILTERED_MESSAGES: - actions.append(_('Preserve')) - - return [ - '

      ' + - _('Policies concerning the content of list traffic.') + - '

      ' + - '

      ' + - _('Content filtering works like this: when a message is received by the list and you have enabled content filtering, the individual attachments are first compared to the filter types. If the attachment type matches an entry in the filter types, it is discarded.') + - '

      ' + - '

      ' + - _('Then, if there are pass types defined, any attachment type that does not match a pass type is also discarded. If there are no pass types defined, this check is skipped.') + - '

      ' + - '

      ' + - _('After this initial filtering, any multipart attachments that are empty are removed. If the outer message is left empty after this filtering, then the whole message is discarded.') + - '

      ' + - '

      ' + - _('Then, each multipart/alternative section will be replaced by just the first alternative that is non-empty after filtering if collapse_alternatives is enabled.') + - '

      ' + - '

      ' + - _('Finally, any text/html parts that are left in the message may be converted to text/plain if convert_html_to_plaintext is enabled and the site is configured to allow these conversions.') + - '

      ', - - ('filter_content', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _("""Should Mailman filter the content of list traffic according to the settings below?""")), - - ('filter_mime_types', mm_cfg.Text, (10, WIDTH), 0, - _("""Remove message attachments that have a matching content type."""), - - _("""Use this option to remove each message attachment that matches one of these content types. Each line should contain a string naming a MIME type/subtype, e.g. image/gif. Leave off the subtype to remove all parts with a matching major content type, e.g. image.""") + '

      ' + - - '

      ' + _('Blank lines are ignored.') + '

      ' + - - '

      ' + _('See also pass_mime_types for a content type whitelist.')), - - ('pass_mime_types', mm_cfg.Text, (10, WIDTH), 0, - _("""Remove message attachments that don't have a matching - content type. Leave this field blank to skip this filter - test."""), - - _("""Use this option to remove each message attachment that does - not have a matching content type. Requirements and formats are - exactly like filter_mime_types.

      - -

      Note: if you add entries to this list but don't add - multipart to this list, any messages with attachments - will be rejected by the pass filter.""")), - - ('filter_filename_extensions', mm_cfg.Text, (10, WIDTH), 0, - _("""Remove message attachments that have a matching filename - extension."""),), - - ('pass_filename_extensions', mm_cfg.Text, (10, WIDTH), 0, - _("""Remove message attachments that don't have a matching - filename extension. Leave this field blank to skip this filter - test."""),), - - ('collapse_alternatives', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _("""Should Mailman collapse multipart/alternative to its - first part content?""")), - - ('convert_html_to_plaintext', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _("""Should Mailman convert text/html parts to plain - text? This conversion happens after MIME attachments have been - stripped.""")), - - ('filter_action', mm_cfg.Radio, tuple(actions), 0, - - _("""Action to take when a message matches the content filtering rules."""), - - _("""One of these actions is taken when the message matches one of the content filtering rules, meaning, the top-level content type matches one of the filter_mime_types, or the top-level content type does not match one of the pass_mime_types, or if after filtering the subparts of the message, the message ends up empty.""") + - '

      ' + - '

      ' + - _('Note this action is not taken if after filtering the message still contains content. In that case the message is always forwarded on to the list membership.') + - '

      ' + - '

      ' + - _('When messages are discarded, a log entry is written containing the Message-ID of the discarded message. When messages are rejected or forwarded to the list owner, a reason for the rejection is included in the bounce message to the original author. When messages are preserved, they are saved in a special queue directory on disk for the site administrator to view (and possibly rescue) but otherwise discarded. This last option is only available if enabled by the site administrator.')), - ] - - def _setValue(self, mlist, property, val, doc): - if property in ('filter_mime_types', 'pass_mime_types'): - types = [] - for spectype in [s.strip() for s in val.splitlines()]: - ok = 1 - slashes = spectype.count('/') - if slashes == 0 and not spectype: - ok = 0 - elif slashes == 1: - maintype, subtype = [s.strip().lower() - for s in spectype.split('/')] - if not maintype or not subtype: - ok = 0 - elif slashes > 1: - ok = 0 - if not ok: - doc.addError(_('Bad MIME type ignored: %(spectype)s')) - else: - types.append(spectype.strip().lower()) - if property == 'filter_mime_types': - mlist.filter_mime_types = types - elif property == 'pass_mime_types': - mlist.pass_mime_types = types - elif property in ('filter_filename_extensions', - 'pass_filename_extensions'): - fexts = [] - for ext in [s.strip() for s in val.splitlines()]: - fexts.append(ext.lower()) - if property == 'filter_filename_extensions': - mlist.filter_filename_extensions = fexts - elif property == 'pass_filename_extensions': - mlist.pass_filename_extensions = fexts - else: - GUIBase._setValue(self, mlist, property, val, doc) - - def getValue(self, mlist, kind, property, params): - if property == 'filter_mime_types': - return NL.join(mlist.filter_mime_types) - if property == 'pass_mime_types': - return NL.join(mlist.pass_mime_types) - if property == 'filter_filename_extensions': - return NL.join(mlist.filter_filename_extensions) - if property == 'pass_filename_extensions': - return NL.join(mlist.pass_filename_extensions) - return None diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Digest.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Digest.py deleted file mode 100644 index 0c43cc2..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Digest.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (C) 1998,1999,2000,2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Administrative GUI for digest deliveries.""" - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.i18n import _ - -# Intra-package import -from Mailman.Gui.GUIBase import GUIBase - -# Common b/w nondigest and digest headers & footers. Personalizations may add -# to this. -ALLOWEDS = ('real_name', 'list_name', 'host_name', 'web_page_url', - 'description', 'info', 'cgiext', '_internal_name', - ) - - - -class Digest(GUIBase): - def GetConfigCategory(self): - return 'digest', _('Digest') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'digest': - return None - WIDTH = mm_cfg.TEXTFIELDWIDTH - - info = [ - '

      ' + _("Batched-delivery digest characteristics.") + '

      ', - - ('digestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1, - _('Can list members choose to receive list traffic ' - 'bunched in digests?')), - - ('digest_is_default', mm_cfg.Radio, - (_('Non-digest'), _('Digest')), 0, - _('Which delivery mode is the default for new users?')), - - ('mime_is_default_digest', mm_cfg.Radio, - (_('Plain'), _('MIME')), 0, - _('When receiving digests, which format is default?')), - - ('digest_size_threshhold', mm_cfg.Number, 3, 0, - _('How big in Kb should a digest be before it gets sent out?')), - # Should offer a 'set to 0' for no size threshhold. - - ('digest_send_periodic', mm_cfg.Radio, (_('No'), _('Yes')), 1, - _('Should a digest be dispatched daily when the size threshold ' - "isn't reached?")), - - ('digest_header', mm_cfg.Text, (4, WIDTH), 0, - _('Header added to every digest'), - _("Text attached (as an initial message, before the table of contents) to the top of digests.

      ") - + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), - - ('digest_footer', mm_cfg.Text, (4, WIDTH), 0, - _('Footer added to every digest'), - _("Text attached (as a final message) to the bottom of digests.

      ") - + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), - - ('digest_volume_frequency', mm_cfg.Radio, - (_('Yearly'), _('Monthly'), _('Quarterly'), - _('Weekly'), _('Daily')), 0, - _('How often should a new digest volume be started?'), - _('''When a new digest volume is started, the volume number is - incremented and the issue number is reset to 1.''')), - - ('_new_volume', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('Should Mailman start a new digest volume?'), - _('''Setting this option instructs Mailman to start a new volume - with the next digest sent out.''')), - - ('_send_digest_now', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('''Should Mailman send the next digest right now, if it is not - empty?''')), - ] - -## if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION: -## info.extend([ -## ('digest_personalize', mm_cfg.Toggle, (_('No'), _('Yes')), 1, - -## _('''Should Mailman personalize each digest delivery? -## This is often useful for announce-only lists, but read the details -## section for a discussion of important performance -## issues.'''), - -## _("""Normally, Mailman sends the digest messages to -## the mail server in batches. This is much more efficent -## because it reduces the amount of traffic between Mailman and -## the mail server. - -##

      However, some lists can benefit from a more personalized -## approach. In this case, Mailman crafts a new message for -## each member on the digest delivery list. Turning this on -## adds a few more expansion variables that can be included in -## the message header -## and message footer -## but it may degrade the performance of your site as -## a whole. - -##

      You need to carefully consider whether the trade-off is -## worth it, or whether there are other ways to accomplish what -## you want. You should also carefully monitor your system load -## to make sure it is acceptable. - -##

      These additional substitution variables will be available -## for your headers and footers, when this feature is enabled: - -##

      • user_address - The address of the user, -## coerced to lower case. -##
      • user_delivered_to - The case-preserved address -## that the user is subscribed with. -##
      • user_password - The user's password. -##
      • user_name - The user's full name. -##
      • user_optionsurl - The url to the user's option -## page. -## """)) -## ]) - - return info - - def _setValue(self, mlist, property, val, doc): - # Watch for the special, immediate action attributes - if property == '_new_volume' and val: - mlist.bump_digest_volume() - volume = mlist.volume - number = mlist.next_digest_number - doc.AddItem(_("""The next digest will be sent as volume - %(volume)s, number %(number)s""")) - elif property == '_send_digest_now' and val: - status = mlist.send_digest_now() - if status: - doc.AddItem(_("""A digest has been sent.""")) - else: - doc.AddItem(_("""There was no digest to send.""")) - else: - # Everything else... - if property in ('digest_header', 'digest_footer'): - val = self._convertString(mlist, property, ALLOWEDS, val, doc) - if val is None: - # There was a problem, so don't set it - return - GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/GUIBase.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/GUIBase.py deleted file mode 100644 index e94fd77..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/GUIBase.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright (C) 2002-2004 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Base class for all web GUI components.""" - -import re -from types import TupleType, ListType - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import Errors -from Mailman.i18n import _ - -NL = '\n' -BADJOINER = ', ' - - - -class GUIBase: - # Providing a common interface for GUI component form processing. Most - # GUI components won't need to override anything, but some may want to - # override _setValue() to provide some specialized processing for some - # attributes. - def _getValidValue(self, mlist, property, wtype, val): - # Coerce and validate the new value. - # - # Radio buttons and boolean toggles both have integral type - if wtype in (mm_cfg.Radio, mm_cfg.Toggle): - # Let ValueErrors propagate - return int(val) - # String and Text widgets both just return their values verbatim - if wtype in (mm_cfg.String, mm_cfg.Text): - return val - # This widget contains a single email address - if wtype == mm_cfg.Email: - # BAW: We must allow blank values otherwise reply_to_address can't - # be cleared. This is currently the only mm_cfg.Email type widget - # in the interface, so watch out if we ever add any new ones. - if val: - # Let MMBadEmailError and MMHostileAddress propagate - Utils.ValidateEmail(val) - return val - # These widget types contain lists of email addresses, one per line. - # The EmailListEx allows each line to contain either an email address - # or a regular expression - if wtype in (mm_cfg.EmailList, mm_cfg.EmailListEx): - # BAW: value might already be a list, if this is coming from - # config_list input. Sigh. - if isinstance(val, ListType): - return val - addrs = [] - for addr in [s.strip() for s in val.split(NL)]: - # Discard empty lines - if not addr: - continue - try: - # This throws an exception if the address is invalid - Utils.ValidateEmail(addr) - except Errors.EmailAddressError: - # See if this is a context that accepts regular - # expressions, and that the re is legal - if wtype == mm_cfg.EmailListEx and addr.startswith('^'): - try: - re.compile(addr) - except re.error: - raise ValueError - else: - raise - addrs.append(addr) - return addrs - # This is a host name, i.e. verbatim - if wtype == mm_cfg.Host: - return val - # This is a number, either a float or an integer - if wtype == mm_cfg.Number: - num = -1 - try: - num = int(val) - except ValueError: - # Let ValueErrors percolate up - num = float(val) - if num < 0: - return getattr(mlist, property) - return num - # This widget is a select box, i.e. verbatim - if wtype == mm_cfg.Select: - return val - # Checkboxes return a list of the selected items, even if only one is - # selected. - if wtype == mm_cfg.Checkbox: - if isinstance(val, ListType): - return val - return [val] - if wtype == mm_cfg.FileUpload: - return val - if wtype == mm_cfg.Topics: - return val - if wtype == mm_cfg.HeaderFilter: - return val - # Should never get here - assert 0, 'Bad gui widget type: %s' % wtype - - def _setValue(self, mlist, property, val, doc): - # Set the value, or override to take special action on the property - if not property.startswith('_') and getattr(mlist, property) <> val: - setattr(mlist, property, val) - - def _postValidate(self, mlist, doc): - # Validate all the attributes for this category - pass - - def _escape(self, property, value): - value = value.replace('<', '<') - return value - - def handleForm(self, mlist, category, subcat, cgidata, doc): - for item in self.GetConfigInfo(mlist, category, subcat): - # Skip descriptions and legacy non-attributes - if not isinstance(item, TupleType) or len(item) < 5: - continue - # Unpack the gui item description - property, wtype, args, deps, desc = item[0:5] - # BAW: I know this code is a little crufty but I wanted to - # reproduce the semantics of the original code in admin.py as - # closely as possible, for now. We can clean it up later. - # - # The property may be uploadable... - uploadprop = property + '_upload' - if cgidata.has_key(uploadprop) and cgidata[uploadprop].value: - val = cgidata[uploadprop].value - elif not cgidata.has_key(property): - continue - elif isinstance(cgidata[property], ListType): - val = [self._escape(property, x.value) - for x in cgidata[property]] - else: - val = self._escape(property, cgidata[property].value) - # Coerce the value to the expected type, raising exceptions if the - # value is invalid. - try: - val = self._getValidValue(mlist, property, wtype, val) - except ValueError: - doc.addError(_('Invalid value for variable: %(property)s')) - # This is the parent of MMBadEmailError and MMHostileAddress - except Errors.EmailAddressError: - doc.addError( - _('Bad email address for option %(property)s: %(val)s')) - else: - # Set the attribute, which will normally delegate to the mlist - self._setValue(mlist, property, val, doc) - # Do a final sweep once all the attributes have been set. This is how - # we can do cross-attribute assertions - self._postValidate(mlist, doc) - - # Convenience method for handling $-string attributes - def _convertString(self, mlist, property, alloweds, val, doc): - # Is the list using $-strings? - dollarp = getattr(mlist, 'use_dollar_strings', 0) - if dollarp: - ids = Utils.dollar_identifiers(val) - else: - # %-strings - ids = Utils.percent_identifiers(val) - # Here's the list of allowable interpolations - for allowed in alloweds: - if ids.has_key(allowed): - del ids[allowed] - if ids: - # What's left are not allowed - badkeys = ids.keys() - badkeys.sort() - bad = BADJOINER.join(badkeys) - doc.addError(_( - """The following illegal substitution variables were - found in the %(property)s string: %(bad)s

        -

        Your list may not operate properly until you correct this - problem."""), tag=_('Warning: ')) - return val - # Now if we're still using %-strings, do a roundtrip conversion and - # see if the converted value is the same as the new value. If not, - # then they probably left off a trailing `s'. We'll warn them and use - # the corrected string. - if not dollarp: - fixed = Utils.to_percent(Utils.to_dollar(val)) - if fixed <> val: - doc.addError(_( - """Your %(property)s string appeared to - have some correctable problems in its new value. - The fixed value will be used instead. Please - double check that this is what you intended. - """)) - return fixed - return val diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/General.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/General.py deleted file mode 100644 index 7f18df4..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/General.py +++ /dev/null @@ -1,452 +0,0 @@ -# Copyright (C) 2001-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""MailList mixin class managing the general options.""" - -import re - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import Errors -from Mailman.i18n import _ -from Mailman.Gui.GUIBase import GUIBase - -OPTIONS = ('hide', 'ack', 'notmetoo', 'nodupes') - - - -class General(GUIBase): - def GetConfigCategory(self): - return 'general', _('Generals') - - def GetConfigInfo(self, mlist, category, subcat): - if category <> 'general': - return None - WIDTH = mm_cfg.TEXTFIELDWIDTH - - # These are for the default_options checkboxes below. - bitfields = {'hide' : mm_cfg.ConcealSubscription, - 'ack' : mm_cfg.AcknowledgePosts, - 'notmetoo' : mm_cfg.DontReceiveOwnPosts, - 'nodupes' : mm_cfg.DontReceiveDuplicates - } - bitdescrs = { - 'hide' : _("Conceal the member's address"), - 'ack' : _("Acknowledge the member's posting"), - 'notmetoo' : _("Do not send a copy of a member's own post"), - 'nodupes' : - _('Filter out duplicate messages to list members (if possible)'), - } - - optvals = [mlist.new_member_options & bitfields[o] for o in OPTIONS] - opttext = [bitdescrs[o] for o in OPTIONS] - - rtn = [ - '

        ' + _('''Fundamental list characteristics, including descriptive - info and basic behaviors.''') + '

        ', - - _('General list personality'), - - ('real_name', mm_cfg.String, WIDTH, 0, - _('The public name of this list (make case-changes only).'), - _('''The capitalization of this name can be changed to make it - presentable in polite company as a proper noun, or to make an - acronym part all upper case, etc. However, the name will be - advertised as the email address (e.g., in subscribe confirmation - notices), so it should not be otherwise altered. (Email - addresses are not case sensitive, but they are sensitive to - almost everything else :-)''')), - - ('owner', mm_cfg.EmailList, (3, WIDTH), 0, - _("""The list administrator email addresses. Multiple - administrator addresses, each on separate line is okay."""), - - _('''There are two ownership roles associated with each mailing - list. The list administrators are the people who have - ultimate control over all parameters of this mailing list. They - are able to change any list configuration variable available - through these administration web pages.

        - -

        The list moderators have more limited permissions; - they are not able to change any list configuration variable, but - they are allowed to tend to pending administration requests, - including approving or rejecting held subscription requests, and - disposing of held postings. Of course, the list - administrators can also tend to pending requests.

        - -

        In order to split the list ownership duties into - administrators and moderators, you must - set a separate moderator password, - and also provide the email - addresses of the list moderators. Note that the field you - are changing here specifies the list administrators.''')), - - ('moderator', mm_cfg.EmailList, (3, WIDTH), 0, - _("""The list moderator email addresses. Multiple - moderator addresses, each on separate line is okay."""), - - _('''There are two ownership roles associated with each mailing - list. The list administrators are the people who have - ultimate control over all parameters of this mailing list. They - are able to change any list configuration variable available - through these administration web pages.

        - -

        The list moderators have more limited permissions; - they are not able to change any list configuration variable, but - they are allowed to tend to pending administration requests, - including approving or rejecting held subscription requests, and - disposing of held postings. Of course, the list - administrators can also tend to pending requests.

        - -

        In order to split the list ownership duties into - administrators and moderators, you must - set a separate moderator password, - and also provide the email addresses of the list moderators in - this section. Note that the field you are changing here - specifies the list moderators.''')), - - ('description', mm_cfg.String, WIDTH, 0, - _('A terse phrase identifying this list.'), - - _('''This description is used when the mailing list is listed with - other mailing lists, or in headers, and so forth. It should - be as succinct as you can get it, while still identifying what - the list is.''')), - - ('info', mm_cfg.Text, (7, WIDTH), 0, - _('''An introductory description - a few paragraphs - about the - list. It will be included, as html, at the top of the listinfo - page. Carriage returns will end a paragraph - see the details - for more info.'''), - _("""The text will be treated as html except that newlines will be translated to <br /> - so you can use links, preformatted text, etc, but don't put in carriage returns except where you mean to separate paragraphs. And review your changes - bad html (like some unterminated HTML constructs) can prevent display of the entire listinfo page.""")), - - ('subject_prefix', mm_cfg.String, WIDTH, 0, - _('Prefix for subject line of list postings.'), - _("""This text will be prepended to subject lines of messages posted to the list, to distinguish mailing list messages in mailbox summaries. Brevity is premium here, it's ok to shorten long mailing list names to something more concise, as long as it still identifies the mailing list.""") + - '

        ' + - '

        ' + - _('You can also add a sequential number by %%d substitution directive. eg.; [listname %%d] -> [listname 123] (listname %%05d) -> (listname 00123).')), - - ('anonymous_list', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _("""Hide the sender of a message, replacing it with the list - address (Removes From, Sender and Reply-To fields)""")), - - _('''Reply-To: header munging'''), - - ('first_strip_reply_to', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('''Should any existing Reply-To: header found in the - original message be stripped? If so, this will be done - regardless of whether an explict Reply-To: header is - added by Mailman or not.''')), - - ('reply_goes_to_list', mm_cfg.Radio, - (_('Poster'), _('This list'), _('Explicit address')), 0, - _('''Where are replies to list messages directed? - Poster is strongly recommended for most mailing - lists.'''), - - # Details for reply_goes_to_list - _("""This option controls what Mailman does to the - Reply-To: header in messages flowing through this - mailing list. When set to Poster, no Reply-To: - header is added by Mailman, although if one is present in the - original message, it is not stripped. Setting this value to - either This list or Explicit address causes - Mailman to insert a specific Reply-To: header in all - messages, overriding the header in the original message if - necessary (Explicit address inserts the value of reply_to_address).

        - -

        There are many reasons not to introduce or override the - Reply-To: header. One is that some posters depend on - their own Reply-To: settings to convey their valid - return address. Another is that modifying Reply-To: - makes it much more difficult to send private replies. See `Reply-To' - Munging Considered Harmful for a general discussion of this - issue. See Reply-To - Munging Considered Useful for a dissenting opinion.

        - -

        Some mailing lists have restricted posting privileges, with a - parallel list devoted to discussions. Examples are `patches' or - `checkin' lists, where software changes are posted by a revision - control system, but discussion about the changes occurs on a - developers mailing list. To support these types of mailing - lists, select Explicit address and set the - Reply-To: address below to point to the parallel - list.""")), - - ('reply_to_address', mm_cfg.Email, WIDTH, 0, - _('Explicit Reply-To: header.'), - # Details for reply_to_address - _("""This is the address set in the Reply-To: header - when the reply_goes_to_list - option is set to Explicit address.

        - -

        There are many reasons not to introduce or override the - Reply-To: header. One is that some posters depend on - their own Reply-To: settings to convey their valid - return address. Another is that modifying Reply-To: - makes it much more difficult to send private replies. See `Reply-To' - Munging Considered Harmful for a general discussion of this - issue. See Reply-To - Munging Considered Useful for a dissenting opinion.

        - -

        Some mailing lists have restricted posting privileges, with a - parallel list devoted to discussions. Examples are `patches' or - `checkin' lists, where software changes are posted by a revision - control system, but discussion about the changes occurs on a - developers mailing list. To support these types of mailing - lists, specify the explicit Reply-To: address here. You - must also specify Explicit address in the - reply_goes_to_list - variable.

        - -

        Note that if the original message contains a - Reply-To: header, it will not be changed.""")), - - _('Umbrella list settings'), - - ('umbrella_list', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('''Send password reminders to, eg, "-owner" address instead of - directly to user.'''), - - _("""Set this to yes when this list is intended to cascade only - to other mailing lists. When set, meta notices like - confirmations and password reminders will be directed to an - address derived from the member\'s address - it will have the - value of "umbrella_member_suffix" appended to the member's - account name.""")), - - ('umbrella_member_suffix', mm_cfg.String, WIDTH, 0, - _('''Suffix for use when this list is an umbrella for other - lists, according to setting of previous "umbrella_list" - setting.'''), - - _("""When "umbrella_list" is set to indicate that this list has - other mailing lists as members, then administrative notices like - confirmations and password reminders need to not be sent to the - member list addresses, but rather to the owner of those member - lists. In that case, the value of this setting is appended to - the member's account name for such notices. `-owner' is the - typical choice. This setting has no effect when "umbrella_list" - is "No".""")), - - _('Notifications'), - - ('send_reminders', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('''Send monthly password reminders?'''), - - _('''Turn this on if you want password reminders to be sent once - per month to your members. Note that members may disable their - own individual password reminders.''')), - - ('welcome_msg', mm_cfg.Text, (4, WIDTH), 0, - _('''List-specific text prepended to new-subscriber welcome - message'''), - - _("""This value, if any, will be added to the front of the new-subscriber welcome message. The rest of the welcome message already describes the important addresses and URLs for the mailing list, so you don't need to include any of that kind of stuff here. This should just contain mission-specific kinds of things, like etiquette policies or team orientation, or that kind of thing.""") + - '

        ' + - '

        ' + - _('Note that this text will be wrapped, according to the following rules:') + - '

        ' + - ''' -
          -
        • ''' + _('Each paragraph is filled so that no line is longer than 70 characters.') + '''
        • -
        • ''' + _('Any line that begins with whitespace is not filled.') + '''
        • -
        • ''' + _('A blank line separates paragraphs.') + '''
        • -
        - -

        '''), - - ('send_welcome_msg', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('Send welcome message to newly subscribed members?'), - _("""Turn this off only if you plan on subscribing people manually - and don't want them to know that you did so. This option is most - useful for transparently migrating lists from some other mailing - list manager to Mailman.""")), - - ('goodbye_msg', mm_cfg.Text, (4, WIDTH), 0, - _('''Text sent to people leaving the list. If empty, no special - text will be added to the unsubscribe message.''')), - - ('send_goodbye_msg', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('Send goodbye message to members when they are unsubscribed?')), - - ('admin_immed_notify', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('''Should the list moderators get immediate notice of new - requests, as well as daily notices about collected ones?'''), - - _('''List moderators (and list administrators) are sent daily - reminders of requests pending approval, like subscriptions to a - moderated list, or postings that are being held for one reason or - another. Setting this option causes notices to be sent - immediately on the arrival of new requests as well.''')), - - ('admin_notify_mchanges', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('''Should administrator get notices of subscribes and - unsubscribes?''')), - - ('respond_to_post_requests', mm_cfg.Radio, - (_('No'), _('Yes')), 0, - _('Send mail to poster when their posting is held for approval?') - ), - - _('Additional settings'), - - ('emergency', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('Emergency moderation of all list traffic.'), - _("""When this option is enabled, all list traffic is emergency - moderated, i.e. held for moderation. Turn this option on when - your list is experiencing a flamewar and you want a cooling off - period.""")), - - ('new_member_options', mm_cfg.Checkbox, - (opttext, optvals, 0, OPTIONS), - # The description for new_member_options includes a kludge where - # we add a hidden field so that even when all the checkboxes are - # deselected, the form data will still have a new_member_options - # key (it will always be a list). Otherwise, we'd never be able - # to tell if all were deselected! - 0, _('''Default options for new members joining this list.'''), - - _("""When a new member is subscribed to this list, their initial - set of options is taken from the this variable's setting.""")), - - ('administrivia', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('''(Administrivia filter) Check postings and intercept ones - that seem to be administrative requests?'''), - - _("""Administrivia tests will check postings to see whether it's - really meant as an administrative request (like subscribe, - unsubscribe, etc), and will add it to the the administrative - requests queue, notifying the administrator of the new request, - in the process.""")), - - ('max_message_size', mm_cfg.Number, 7, 0, - _('''Maximum length in kilobytes (KB) of a message body. Use 0 - for no limit.''')), - - ('host_name', mm_cfg.Host, WIDTH, 0, - _('Host name this list prefers for email.'), - - _("""The "host_name" is the preferred name for email to - mailman-related addresses on this host, and generally should be - the mail host's exchanger address, if any. This setting can be - useful for selecting among alternative names of a host that has - multiple addresses.""")), - - ] - - if mm_cfg.ALLOW_RFC2369_OVERRIDES: - rtn.append( - ('include_rfc2369_headers', mm_cfg.Radio, - (_('No'), _('Yes')), 0, - _("""Should messages from this mailing list include the - RFC 2369 - (i.e. List-*) headers? Yes is highly - recommended."""), - - _("""RFC 2369 defines a set of List-* headers that are - normally added to every message sent to the list membership. - These greatly aid end-users who are using standards compliant - mail readers. They should normally always be enabled.

        - -

        However, not all mail readers are standards compliant yet, - and if you have a large number of members who are using - non-compliant mail readers, they may be annoyed at these - headers. You should first try to educate your members as to - why these headers exist, and how to hide them in their mail - clients. As a last resort you can disable these headers, but - this is not recommended (and in fact, your ability to disable - these headers may eventually go away).""")) - ) - # Suppression of List-Post: headers - rtn.append( - ('include_list_post_header', mm_cfg.Radio, - (_('No'), _('Yes')), 0, - _('Should postings include the List-Post: header?'), - _("""The List-Post: header is one of the headers - recommended by - RFC 2369. - However for some announce-only mailing lists, only a - very select group of people are allowed to post to the list; the - general membership is usually not allowed to post. For lists of - this nature, the List-Post: header is misleading. - Select No to disable the inclusion of this header. (This - does not affect the inclusion of the other List-*: - headers.)""")) - ) - - # Discard held messages after this number of days - rtn.append( - ('max_days_to_hold', mm_cfg.Number, 7, 0, - _("""Discard held messages older than this number of days. - Use 0 for no automatic discarding.""")) - ) - - return rtn - - def _setValue(self, mlist, property, val, doc): - if property == 'real_name' and \ - val.lower() <> mlist.internal_name().lower(): - # These values can't differ by other than case - doc.addError(_("""real_name attribute not - changed! It must differ from the list's name by case - only.""")) - elif property == 'new_member_options': - newopts = 0 - for opt in OPTIONS: - bitfield = mm_cfg.OPTINFO[opt] - if opt in val: - newopts |= bitfield - mlist.new_member_options = newopts - elif property == 'subject_prefix': - # Convert any html entities to Unicode - mlist.subject_prefix = Utils.canonstr( - val, mlist.preferred_language) - else: - GUIBase._setValue(self, mlist, property, val, doc) - - def _escape(self, property, value): - # The 'info' property allows HTML, but let's sanitize it to avoid XSS - # exploits. Everything else should be fully escaped. - if property <> 'info': - return GUIBase._escape(self, property, value) - # Sanitize tags but nothing else. Not the best - # solution, but expedient. - return re.sub(r'(?i)<([/]?script.*?)>', r'<\1>', value) - - def _postValidate(self, mlist, doc): - if not mlist.reply_to_address.strip() and \ - mlist.reply_goes_to_list == 2: - # You can't go to an explicit address that is blank - doc.addError(_("""You cannot add a Reply-To: to an explicit - address if that address is blank. Resetting these values.""")) - mlist.reply_to_address = '' - mlist.reply_goes_to_list = 0 - - def getValue(self, mlist, kind, varname, params): - if varname <> 'subject_prefix': - return None - # The subject_prefix may be Unicode - return Utils.uncanonstr(mlist.subject_prefix, mlist.preferred_language) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Language.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Language.py deleted file mode 100644 index 2a8e042..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Language.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (C) 2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""MailList mixin class managing the language options. -""" - -import codecs - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import i18n -from Mailman.Logging.Syslog import syslog -from Mailman.Gui.GUIBase import GUIBase - -_ = i18n._ - - - -class Language(GUIBase): - def GetConfigCategory(self): - return 'language', _('Languages') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'language': - return None - - # Set things up for the language choices - langs = mlist.GetAvailableLanguages() - langnames = [_(Utils.GetLanguageDescr(L)) for L in langs] - try: - langi = langs.index(mlist.preferred_language) - except ValueError: - # Someone must have deleted the list's preferred language. Could - # be other trouble lurking! - langi = 0 - - # Only allow the admin to choose a language if the system has a - # charset for it. I think this is the best way to test for that. - def checkcodec(charset): - try: - codecs.lookup(charset) - return 1 - except LookupError: - return 0 - - all = [key for key in mm_cfg.LC_DESCRIPTIONS.keys() - if checkcodec(Utils.GetCharSet(key))] - all.sort() - checked = [L in langs for L in all] - allnames = [_(Utils.GetLanguageDescr(L)) for L in all] - - return [ - '

        ' + _('Natural language (internationalization) options.') + '

        ', - - ('preferred_language', mm_cfg.Select, - (langs, langnames, langi), - 0, - _('Default language for this list.'), - _('''This is the default natural language for this mailing list. - If more than one - language is supported then users will be able to select their - own preferences for when they interact with the list. All other - interactions will be conducted in the default language. This - applies to both web-based and email-based messages, but not to - email posted by list members.''')), - - ('available_languages', mm_cfg.Checkbox, - (allnames, checked, 0, all), 0, - _('Languages supported by this list.'), - - _('''These are all the natural languages supported by this list. - Note that the - default - language must be included.''')), - - ('encode_ascii_prefixes', mm_cfg.Radio, - (_('Never'), _('Always'), _('As needed')), 0, - _("""Encode the - subject - prefix even when it consists of only ASCII characters?"""), - - _("""If your mailing list's default language uses a non-ASCII - character set and the prefix contains non-ASCII characters, the - prefix will always be encoded according to the relevant - standards. However, if your prefix contains only ASCII - characters, you may want to set this option to Never to - disable prefix encoding. This can make the subject headers - slightly more readable for users with mail readers that don't - properly handle non-ASCII encodings.

        - -

        Note however, that if your mailing list receives both encoded - and unencoded subject headers, you might want to choose As - needed. Using this setting, Mailman will not encode ASCII - prefixes when the rest of the header contains only ASCII - characters, but if the original header contains non-ASCII - characters, it will encode the prefix. This avoids an ambiguity - in the standards which could cause some mail readers to display - extra, or missing spaces between the prefix and the original - header.""")), - - ] - - def _setValue(self, mlist, property, val, doc): - # If we're changing the list's preferred language, change the I18N - # context as well - if property == 'preferred_language': - i18n.set_language(val) - doc.set_language(val) - GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Membership.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Membership.py deleted file mode 100644 index a061ad3..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Membership.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (C) 2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""MailList mixin class managing the membership pseudo-options. -""" - -from Mailman.i18n import _ - - - -class Membership: - def GetConfigCategory(self): - return 'members', _('Membership...') - - def GetConfigSubCategories(self, category): - if category == 'members': - return [('list', _('Membership List')), - ('add', _('Mass Subscription')), - ('remove', _('Mass Removal')), - ] - return None diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/NonDigest.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/NonDigest.py deleted file mode 100644 index 886428a..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/NonDigest.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright (C) 2001-2003 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -""" GUI component for managing the non-digest delivery options. -""" - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.i18n import _ -from Mailman.Gui.GUIBase import GUIBase - -from Mailman.Gui.Digest import ALLOWEDS -PERSONALIZED_ALLOWEDS = ('user_address', 'user_delivered_to', 'user_password', - 'user_name', 'user_optionsurl', - ) - - - -class NonDigest(GUIBase): - def GetConfigCategory(self): - return 'nondigest', _('Non-digest') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'nondigest': - return None - WIDTH = mm_cfg.TEXTFIELDWIDTH - - info = [ - '

        ' + _("Policies concerning immediately delivered list traffic.") + '

        ', - - ('nondigestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1, - _("""Can subscribers choose to receive mail immediately, rather - than in batched digests?""")), - ] - - if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION: - info.extend([ - ('personalize', mm_cfg.Radio, - (_('No'), _('Yes'), _('Full Personalization')), 1, - - _('''Should Mailman personalize each non-digest delivery? - This is often useful for announce-only lists, but read the details - section for a discussion of important performance - issues.'''), - - _("""Normally, Mailman sends the regular delivery messages to - the mail server in batches. This is much more efficent - because it reduces the amount of traffic between Mailman and - the mail server.

        - -

        However, some lists can benefit from a more personalized - approach. In this case, Mailman crafts a new message for - each member on the regular delivery list. Turning this - feature on may degrade the performance of your site, so you - need to carefully consider whether the trade-off is worth it, - or whether there are other ways to accomplish what you want. - You should also carefully monitor your system load to make - sure it is acceptable.

        - -

        Select No to disable personalization and send - messages to the members in batches. Select Yes to - personalize deliveries and allow additional substitution - variables in message headers and footers (see below). In - addition, by selecting Full Personalization, the - To header of posted messages will be modified to - include the member's address instead of the list's posting - address.

        - -

        When personalization is enabled, a few more expansion - variables that can be included in the message header and - message footer.

        - -

        These additional substitution variables will be available - for your headers and footers, when this feature is enabled:

        - -
          -
        • user_address - The address of the user, - coerced to lower case.
        • -
        • user_delivered_to - The case-preserved address - that the user is subscribed with.
        • -
        • user_password - The user's password.
        • -
        • user_name - The user's full name.
        • -
        • user_optionsurl - The url to the user's option - page.
        • -
        - -

        """))]) - # BAW: for very dumb reasons, we want the `personalize' attribute to - # show up before the msg_header and msg_footer attrs, otherwise we'll - # get a bogus warning if the header/footer contains a personalization - # substitution variable, and we're transitioning from no - # personalization to personalization enabled. - headfoot = Utils.maketext('headfoot.html', mlist=mlist, raw=1) - if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION: - extra = _("""\ -

        When personalization is enabled - for this list, additional substitution variables are allowed in your headers - and footers:

        - -
          -
        • user_address - The address of the user, coerced to lower case.
        • -
        • user_delivered_to - The case-preserved address that the user is subscribed with.
        • -
        • user_password - The user's password.
        • -
        • user_name - The user's full name.
        • -
        • user_optionsurl - The url to the user's option page.
        • -
        - -

        """) - else: - extra = '' - - info.extend([('msg_header', mm_cfg.Text, (10, WIDTH), 0, - _('Header added to mail sent to regular list members'), - _('''Text prepended to the top of every immediately-delivery message.

        ''') + headfoot + extra), - - ('msg_footer', mm_cfg.Text, (10, WIDTH), 0, - _('Footer added to mail sent to regular list members'), - _('''Text appended to the bottom of every immediately-delivery message.

        ''') + headfoot + extra), - ]) - - info.extend([ - ('scrub_nondigest', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('Scrub attachments of regular delivery message?'), - _('''When you scrub attachments, they are stored in archive - area and links are made in the message so that the member can - access via web browser. If you want the attachments totally - disappear, you can use content filter options.''')), - ]) - return info - - def _setValue(self, mlist, property, val, doc): - alloweds = list(ALLOWEDS) - if mlist.personalize: - alloweds.extend(PERSONALIZED_ALLOWEDS) - if property in ('msg_header', 'msg_footer'): - val = self._convertString(mlist, property, alloweds, val, doc) - if val is None: - # There was a problem, so don't set it - return - GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Passwords.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Passwords.py deleted file mode 100644 index 8ea9bcf..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Passwords.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (C) 2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""MailList mixin class managing the password pseudo-options. -""" - -from Mailman.i18n import _ -from Mailman.Gui.GUIBase import GUIBase - - - -class Passwords(GUIBase): - def GetConfigCategory(self): - return 'passwords', _('Passwords') - - def handleForm(self, mlist, category, subcat, cgidata, doc): - # Nothing more needs to be done - pass diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Privacy.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Privacy.py deleted file mode 100644 index a55a73f..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Privacy.py +++ /dev/null @@ -1,510 +0,0 @@ -# Copyright (C) 2001-2005 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""MailList mixin class managing the privacy options.""" - -import re - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.i18n import _ -from Mailman.Gui.GUIBase import GUIBase - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -class Privacy(GUIBase): - def GetConfigCategory(self): - return 'privacy', _('Privacy...') - - def GetConfigSubCategories(self, category): - if category == 'privacy': - return [('subscribing', _('Subscription rules')), - ('sender', _('Sender filters')), - ('recipient', _('Recipient filters')), - ('spam', _('Spam filters')), - ] - return None - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'privacy': - return None - # Pre-calculate some stuff. Technically, we shouldn't do the - # sub_cfentry calculation here, but it's too ugly to indent it any - # further, and besides, that'll mess up i18n catalogs. - WIDTH = mm_cfg.TEXTFIELDWIDTH - if mm_cfg.ALLOW_OPEN_SUBSCRIBE: - sub_cfentry = ('subscribe_policy', mm_cfg.Radio, - # choices - (_('None'), - _('Confirm'), - _('Require approval'), - _('Confirm and approve')), - 0, - _('What steps are required for subscription?'), - _('Available options') + ':' + - ''' -

        - -
          -
        • ''' + _('None - no verification steps (Not Recommended)') + '''
        • -
        • ''' + _('Confirm (*) - email confirmation step required') + '''
        • -
        • ''' + _('Require approval - require list administrator approval for subscriptions') + '''
        • -
        • ''' + _('Confirm and approve - both confirm and approve') + '''
        • - -
        - -

        ''' + _('(*) when someone requests a subscription, Mailman sends them a notice with a unique subscription request number that they must reply to in order to subscribe. This prevents mischievous (or malicious) people from creating subscriptions for others without their consent.')) - else: - sub_cfentry = ('subscribe_policy', mm_cfg.Radio, - # choices - (_('Confirm'), - _('Require approval'), - _('Confirm and approve')), - 1, - _('What steps are required for subscription?'), - _('Available options') + ':' + - ''' -

        - -
          -
        • ''' + _('None - no verification steps (Not Recommended)') + '''
        • -
        • ''' + _('Confirm (*) - email confirmation step required') + '''
        • -
        • ''' + _('Require approval - require list administrator approval for subscriptions') + '''
        • -
        • ''' + _('Confirm and approve - both confirm and approve') + '''
        • - -
        - -

        ''' + _('(*) when someone requests a subscription, Mailman sends them a notice with a unique subscription request number that they must reply to in order to subscribe. This prevents mischievous (or malicious) people from creating subscriptions for others without their consent.')) - - # some helpful values - admin = mlist.GetScriptURL('admin') - - subscribing_rtn = [ - '

        ' + _("""This section allows you to configure subscription and membership exposure policy. You can also control whether this list is public or not. See also the Archival Options section for separate archive-related privacy settings.""") + '

        ', - - _('Subscribing'), - ('advertised', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _('''Advertise this list when people ask what lists are on this - machine?''')), - - sub_cfentry, - - ('unsubscribe_policy', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _("""Is the list moderator's approval required for unsubscription - requests? (No is recommended)"""), - - _("""When members want to leave a list, they will make an - unsubscription request, either via the web or via email. - Normally it is best for you to allow open unsubscriptions so that - users can easily remove themselves from mailing lists (they get - really upset if they can't get off lists!).

        - -

        For some lists though, you may want to impose moderator - approval before an unsubscription request is processed. Examples - of such lists include a corporate mailing list that all employees - are required to be members of.""")), - - _('Ban list'), - ('ban_list', mm_cfg.EmailListEx, (10, WIDTH), 1, - _("""List of addresses which are banned from membership in this - mailing list."""), - - _("""Addresses in this list are banned outright from subscribing - to this mailing list, with no further moderation required. Add - addresses one per line; start the line with a ^ character to - designate a regular expression match.""")), - - _("Membership exposure"), - ('private_roster', mm_cfg.Radio, - (_('Anyone'), _('List members'), _('List admin only')), 0, - _('Who can view subscription list?'), - - _('''When set, the list of subscribers is protected by member or - admin password authentication.''')), - - ('obscure_addresses', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _("""Show member addresses so they're not directly recognizable - as email addresses?"""), - _("""Setting this option causes member email addresses to be - transformed when they are presented on list web pages (both in - text and as links), so they're not trivially recognizable as - email addresses. The intention is to prevent the addresses - from being snarfed up by automated web scanners for use by - spammers.""")), - ] - - adminurl = mlist.GetScriptURL('admin', absolute=1) - sender_rtn = [ - _("""

        When a message is posted to the list, a series of - moderation steps are take to decide whether the a moderator must - first approve the message or not. This section contains the - controls for moderation of both member and non-member postings.

        - -

        Member postings are held for moderation if their - moderation flag is turned on. You can control whether - member postings are moderated by default or not.

        - -

        Non-member postings can be automatically - accepted, - held for - moderation, - rejected (bounced), or - discarded, - either individually or as a group. Any - posting from a non-member who is not explicitly accepted, - rejected, or discarded, will have their posting filtered by the - general - non-member rules.

        - -

        In the text boxes below, add one address per line; start the - line with a ^ character to designate a Python regular expression. When entering backslashes, do so - as if you were using Python raw strings (i.e. you generally just - use a single backslash).

        - -

        Note that non-regexp matches are always done first.

        """), - - _('Member filters'), - - ('default_member_moderation', mm_cfg.Radio, (_('No'), _('Yes')), - 0, _('By default, should new list member postings be moderated?'), - - _("""Each list member has a moderation flag which says - whether messages from the list member can be posted directly to - the list, or must first be approved by the list moderator. When - the moderation flag is turned on, list member postings must be - approved first. You, the list administrator can decide whether a - specific individual's postings will be moderated or not.

        - -

        When a new member is subscribed, their initial moderation flag - takes its value from this option. Turn this option off to accept - member postings by default. Turn this option on to, by default, - moderate member postings first. You can always manually set an - individual member's moderation bit by using the - membership management - screens.""")), - - ('member_moderation_action', mm_cfg.Radio, - (_('Hold'), _('Reject'), _('Discard')), 0, - _("""Action to take when a moderated member posts to the - list."""), - _('Available options') + ':

        ' + - ''' -
          -
        • ''' + _('Hold') + ''' -- ''' + _('this holds the message for approval by the list moderators.') + '''
        • -
        • ''' + _('Reject') + ''' -- ''' + _('''this automatically rejects the message by sending a bounce notice to the post's author. The text of the bounce notice can be configured by you.''') + '''
        • -
        • ''' + _('Discard') + ''' -- ''' + _('''this simply discards the message, with no notice sent to the post's author.''') + '''
        • -
        - -

        - '''), - - ('member_moderation_notice', mm_cfg.Text, (10, WIDTH), 1, - _("""Text to include in any - rejection notice to - be sent to moderated members who post to this list.""")), - - _('Non-member filters'), - - ('accept_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, - _("""List of non-member addresses whose postings should be - automatically accepted."""), - - _("""Postings from any of these non-members will be automatically accepted with no further moderation applied.""") + - '

        ' + - '

        ' + - _('Add member addresses one per line; start the line with a ^ character to designate a regular expression match.')), - - ('hold_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, - _("""List of non-member addresses whose postings will be - immediately held for moderation."""), - - _("""Postings from any of these non-members will be immediately and automatically held for moderation by the list moderators. The sender will receive a notification message which will allow them to cancel their held message.""") + - '

        ' + - '

        ' + - _('Add member addresses one per line; start the line with a ^ character to designate a regular expression match.')), - - ('reject_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, - _("""List of non-member addresses whose postings will be - automatically rejected."""), - - _("""Postings from any of these non-members will be automatically rejected. In other words, their messages will be bounced back to the sender with a notification of automatic rejection. This option is not appropriate for known spam senders; their messages should be automatically discarded.""") + - '

        ' + - '

        ' + - _('Add member addresses one per line; start the line with a ^ character to designate a regular expression match.')), - - ('discard_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, - _("""List of non-member addresses whose postings will be - automatically discarded."""), - - _("""Postings from any of these non-members will be automatically discarded. That is, the message will be thrown away with no further processing or notification. The sender will not receive a notification or a bounce, however the list moderators can optionally receive copies of auto-discarded messages.""") + - '

        ' + - '

        ' + - _('Add member addresses one per line; start the line with a ^ character to designate a regular expression match.')), - - ('generic_nonmember_action', mm_cfg.Radio, - (_('Accept'), _('Hold'), _('Reject'), _('Discard')), 0, - _("""Action to take for postings from non-members for which no - explicit action is defined."""), - - _("""When a post from a non-member is received, the message's - sender is matched against the list of explicitly - accepted, - held, - rejected (bounced), and - discarded addresses. If no match is found, then this action - is taken.""")), - - ('forward_auto_discards', mm_cfg.Radio, (_('No'), _('Yes')), 0, - _("""Should messages from non-members, which are automatically - discarded, be forwarded to the list moderator?""")), - - ('nonmember_rejection_notice', mm_cfg.Text, (10, WIDTH), 1, - _("""Text to include in any rejection notice to be sent to - non-members who post to this list. This notice can include - the list's owner address by %%(listowner)s and replaces the - internally crafted default message.""")), - - ] - - recip_rtn = [ - '

        ' + - _("""This section allows you to configure various filters based on the recipient of the message.""") + - '

        ', - - _('Recipient filters'), - - ('require_explicit_destination', mm_cfg.Radio, - (_('No'), _('Yes')), 0, - _("""Must posts have list named in destination (to, cc) field - (or be among the acceptable alias names, specified below)?"""), - - _("""Many (in fact, most) spams do not explicitly name their myriad destinations in the explicit destination addresses - in fact often the To: field has a totally bogus address for obfuscation. The constraint applies only to the stuff in the address before the '@' sign, but still catches all such spams.""") + - '

        ' + - '

        ' + - _('The cost is that the list will not accept unhindered any postings relayed from other addresses, unless:') + '''

        - -
          -
        1. ''' + _('The relaying address has the same name, or') + '''
        2. -
        3. ''' + _('The relaying address name is included on the options that specifies acceptable aliases for the list.') + '''
        4. - -
        -

        - '''), - - ('acceptable_aliases', mm_cfg.Text, (4, WIDTH), 0, - _("""Alias names (regexps) which qualify as explicit to or cc - destination names for this list."""), - - _("""Alternate addresses that are acceptable when - `require_explicit_destination' is enabled. This option takes a - list of regular expressions, one per line, which is matched - against every recipient address in the message. The matching is - performed with Python's re.match() function, meaning they are - anchored to the start of the string.

        - -

        For backwards compatibility with Mailman 1.1, if the regexp - does not contain an `@', then the pattern is matched against just - the local part of the recipient address. If that match fails, or - if the pattern does contain an `@', then the pattern is matched - against the entire recipient address.

        - -

        Matching against the local part is deprecated; in a future - release, the pattern will always be matched against the entire - recipient address.""")), - - ('max_num_recipients', mm_cfg.Number, 5, 0, - _('Ceiling on acceptable number of recipients for a posting.'), - - _('''If a posting has this number, or more, of recipients, it is - held for admin approval. Use 0 for no ceiling.''')), - ] - - spam_rtn = [ - '

        ' + - _('''This section allows you to configure various anti-spam filters posting filters, which can help reduce the amount of spam your list members end up receiving.''') + - '

        ', - - _('Header filters'), - - ('header_filter_rules', mm_cfg.HeaderFilter, 0, 0, - _('Filter rules to match against the headers of a message.'), - - _("""Each header filter rule has two parts, a list of regular - expressions, one per line, and an action to take. Mailman - matches the message's headers against every regular expression in - the rule and if any match, the message is rejected, held, or - discarded based on the action you specify. Use Defer to - temporarily disable a rule.

        - -

        You can have more than one filter rule for your list. In that - case, each rule is matched in turn, with processing stopped after - the first match.

        - -

        Note that headers are collected from all the attachments - (except for the mailman administrivia message) and - matched against the regular expressions. With this feature, - you can effectively sort out messages with dangerous file - types or file name extensions.""")), - - _('Legacy anti-spam filters'), - - ('bounce_matching_headers', mm_cfg.Text, (6, WIDTH), 0, - _('Hold posts with header value matching a specified regexp.'), - _("""Use this option to prohibit posts according to specific header values. The target value is a regular-expression for matching against the specified header. The match is done disregarding letter case. Lines beginning with '#' are ignored as comments.""") + - '

        ' + - '

        ' + - _('For example:') + - '

        ' + - ''' -
        to: .*@public.com
        -

        - ''' + - _("""says to hold all postings with a To: mail header containing '@public.com' anywhere among the addresses.""") + - '

        ' + '

        ' + - _('Note that leading whitespace is trimmed from the regexp. This can be circumvented in a number of ways, e.g. by escaping or bracketing it.')), - ] - - if subcat == 'sender': - return sender_rtn - elif subcat == 'recipient': - return recip_rtn - elif subcat == 'spam': - return spam_rtn - else: - return subscribing_rtn - - def _setValue(self, mlist, property, val, doc): - # Ignore any hdrfilter_* form variables - if property.startswith('hdrfilter_'): - return - # For subscribe_policy when ALLOW_OPEN_SUBSCRIBE is true, we need to - # add one to the value because the page didn't present an open list as - # an option. - if property == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE: - val += 1 - setattr(mlist, property, val) - - # We need to handle the header_filter_rules widgets specially, but - # everything else can be done by the base class's handleForm() method. - # However, to do this we need an awful hack. _setValue() and - # _getValidValue() will essentially ignore any hdrfilter_* form variables. - # TK: we should call this function only in subcat == 'spam' - def _handleForm(self, mlist, category, subcat, cgidata, doc): - # TK: If there is no hdrfilter_* in cgidata, we should not touch - # the header filter rules. - if not cgidata.has_key('hdrfilter_rebox_01'): - return - # First deal with - rules = [] - # We start i at 1 and keep going until we no longer find items keyed - # with the marked tags. - i = 1 - downi = None - while True: - deltag = 'hdrfilter_delete_%02d' % i - reboxtag = 'hdrfilter_rebox_%02d' % i - actiontag = 'hdrfilter_action_%02d' % i - wheretag = 'hdrfilter_where_%02d' % i - addtag = 'hdrfilter_add_%02d' % i - newtag = 'hdrfilter_new_%02d' % i - uptag = 'hdrfilter_up_%02d' % i - downtag = 'hdrfilter_down_%02d' % i - i += 1 - # Was this a delete? If so, we can just ignore this entry - if cgidata.has_key(deltag): - continue - # Get the data for the current box - pattern = cgidata.getvalue(reboxtag) - try: - action = int(cgidata.getvalue(actiontag)) - # We'll get a TypeError when the actiontag is missing and the - # .getvalue() call returns None. - except (ValueError, TypeError): - action = mm_cfg.DEFER - if pattern is None: - # We came to the end of the boxes - break - if cgidata.has_key(newtag) and not pattern: - # This new entry is incomplete. - if i == 2: - # OK it is the first. - continue - doc.addError(_("""Header filter rules require a pattern. - Incomplete filter rules will be ignored.""")) - continue - # Make sure the pattern was a legal regular expression - try: - re.compile(pattern) - except (re.error, TypeError): - safepattern = Utils.websafe(pattern) - doc.addError(_("""The header filter rule pattern - '%(safepattern)s' is not a legal regular expression. This - rule will be ignored.""")) - continue - # Was this an add item? - if cgidata.has_key(addtag): - # Where should the new one be added? - where = cgidata.getvalue(wheretag) - if where == 'before': - # Add a new empty rule box before the current one - rules.append(('', mm_cfg.DEFER, True)) - rules.append((pattern, action, False)) - # Default is to add it after... - else: - rules.append((pattern, action, False)) - rules.append(('', mm_cfg.DEFER, True)) - # Was this an up movement? - elif cgidata.has_key(uptag): - # As long as this one isn't the first rule, move it up - if rules: - rules.insert(-1, (pattern, action, False)) - else: - rules.append((pattern, action, False)) - # Was this the down movement? - elif cgidata.has_key(downtag): - downi = i - 2 - rules.append((pattern, action, False)) - # Otherwise, just retain this one in the list - else: - rules.append((pattern, action, False)) - # Move any down button filter rule - if downi is not None: - rule = rules[downi] - del rules[downi] - rules.insert(downi+1, rule) - mlist.header_filter_rules = rules - - def handleForm(self, mlist, category, subcat, cgidata, doc): - if subcat == 'spam': - self._handleForm(mlist, category, subcat, cgidata, doc) - # Everything else is dealt with by the base handler - GUIBase.handleForm(self, mlist, category, subcat, cgidata, doc) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Topics.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Topics.py deleted file mode 100644 index eb1d4ce..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Topics.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright (C) 2001-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -import re - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.i18n import _ -from Mailman.Logging.Syslog import syslog -from Mailman.Gui.GUIBase import GUIBase - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -class Topics(GUIBase): - def GetConfigCategory(self): - return 'topics', _('Topics') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'topics': - return None - WIDTH = mm_cfg.TEXTFIELDWIDTH - - return [ - '

        ' + - _('List topic keywords.') + - '

        ', - - ('topics_enabled', mm_cfg.Radio, (_('Disabled'), _('Enabled')), 0, - _('''Should the topic filter be enabled or disabled?'''), - - _("""The topic filter categorizes each incoming email message according to regular expression filters you specify below. If the message's Subject: or Keywords: header contains a match against a topic filter, the message is logically placed into a topic bucket. Each user can then choose to only receive messages from the mailing list for a particular topic bucket (or buckets). Any message not categorized in a topic bucket registered with the user is not delivered to the list.""") + - '

        ' + - '

        ' + - _('Note that this feature only works with regular delivery, not digest delivery.') + - '

        ' + - '

        ' + - _('The body of the message can also be optionally scanned for Subject: and Keywords: headers, as specified by the topics_bodylines_limit configuration variable.')), - - ('topics_bodylines_limit', mm_cfg.Number, 5, 0, - _('How many body lines should the topic matcher scan?'), - - _("""The topic matcher will scan this many lines of the message - body looking for topic keyword matches. Body scanning stops when - either this many lines have been looked at, or a non-header-like - body line is encountered. By setting this value to zero, no body - lines will be scanned (i.e. only the Keywords: and - Subject: headers will be scanned). By setting this - value to a negative number, then all body lines will be scanned - until a non-header-like line is encountered. - """)), - - ('topics', mm_cfg.Topics, 0, 0, - _('Topic keywords, one per line, to match against each message.'), - - _("""Each topic keyword is actually a regular expression, which is - matched against certain parts of a mail message, specifically the - Keywords: and Subject: message headers. - Note that the first few lines of the body of the message can also - contain a Keywords: and Subject: - "header" on which matching is also performed.""")), - - ] - - def handleForm(self, mlist, category, subcat, cgidata, doc): - # MAS: Did we come from the authentication page? - if not cgidata.has_key('topic_box_01'): - return - topics = [] - # We start i at 1 and keep going until we no longer find items keyed - # with the marked tags. - i = 1 - while True: - deltag = 'topic_delete_%02d' % i - boxtag = 'topic_box_%02d' % i - reboxtag = 'topic_rebox_%02d' % i - desctag = 'topic_desc_%02d' % i - wheretag = 'topic_where_%02d' % i - addtag = 'topic_add_%02d' % i - newtag = 'topic_new_%02d' % i - i += 1 - # Was this a delete? If so, we can just ignore this entry - if cgidata.has_key(deltag): - continue - # Get the data for the current box - name = cgidata.getvalue(boxtag) - pattern = cgidata.getvalue(reboxtag) - desc = cgidata.getvalue(desctag) - if name is None: - # We came to the end of the boxes - break - if cgidata.has_key(newtag) and (not name or not pattern): - # This new entry is incomplete. - doc.addError(_("""Topic specifications require both a name and - a pattern. Incomplete topics will be ignored.""")) - continue - # Make sure the pattern was a legal regular expression - name = Utils.websafe(name) - try: - re.compile(pattern) - except (re.error, TypeError): - safepattern = Utils.websafe(pattern) - doc.addError(_("""The topic pattern '%(safepattern)s' is not a - legal regular expression. It will be discarded.""")) - continue - # Was this an add item? - if cgidata.has_key(addtag): - # Where should the new one be added? - where = cgidata.getvalue(wheretag) - if where == 'before': - # Add a new empty topics box before the current one - topics.append(('', '', '', True)) - topics.append((name, pattern, desc, False)) - # Default is to add it after... - else: - topics.append((name, pattern, desc, False)) - topics.append(('', '', '', True)) - # Otherwise, just retain this one in the list - else: - topics.append((name, pattern, desc, False)) - # Add these topics to the mailing list object, and deal with other - # options. - mlist.topics = topics - try: - mlist.topics_enabled = int(cgidata.getvalue( - 'topics_enabled', - mlist.topics_enabled)) - except ValueError: - # BAW: should really print a warning - pass - try: - mlist.topics_bodylines_limit = int(cgidata.getvalue( - 'topics_bodylines_limit', - mlist.topics_bodylines_limit)) - except ValueError: - # BAW: should really print a warning - pass diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Usenet.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Usenet.py deleted file mode 100644 index 2924d81..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/Usenet.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright (C) 2001-2003 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -from Mailman import mm_cfg -from Mailman.i18n import _ -from Mailman.Gui.GUIBase import GUIBase - - - -class Usenet(GUIBase): - def GetConfigCategory(self): - return 'gateway', _('Mail<->News gateways') - - def GetConfigInfo(self, mlist, category, subcat=None): - if category <> 'gateway': - return None - - WIDTH = mm_cfg.TEXTFIELDWIDTH - VERTICAL = 1 - - return [ - '

        ' + - _('Mail-to-News and News-to-Mail gateway services.') + - '

        ', - - _('News server settings'), - - ('nntp_host', mm_cfg.String, WIDTH, 0, - _('The hostname of the machine your news server is running on.'), - _('''This value may be either the name of your news server, or - optionally of the format name:port, where port is a port number.

        - -

        The news server is not part of Mailman proper. You have to - already have access to an NNTP server, and that NNTP server must - recognize the machine this mailing list runs on as a machine - capable of reading and posting news.''')), - - ('linked_newsgroup', mm_cfg.String, WIDTH, 0, - _('The name of the Usenet group to gateway to and/or from.')), - - ('gateway_to_news', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('''Should new posts to the mailing list be sent to the - newsgroup?''')), - - ('gateway_to_mail', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('''Should new posts to the newsgroup be sent to the mailing - list?''')), - - _('Forwarding options'), - - ('news_moderation', mm_cfg.Radio, - (_('None'), _('Open list, moderated group'), _('Moderated')), - VERTICAL, - - _("""The moderation policy of the newsgroup."""), - - _("""This setting determines the moderation policy of the - newsgroup and its interaction with the moderation policy of the - mailing list. This only applies to the newsgroup that you are - gatewaying to, so if you are only gatewaying from - Usenet, or the newsgroup you are gatewaying to is not moderated, - set this option to None.

        - -

        If the newsgroup is moderated, you can set this mailing list - up to be the moderation address for the newsgroup. By selecting - Moderated, an additional posting hold will be placed in - the approval process. All messages posted to the mailing list - will have to be approved before being sent on to the newsgroup, - or to the mailing list membership.

        - -

        Note that if the message has an Approved header - with the list's administrative password in it, this hold test - will be bypassed, allowing privileged posters to send messages - directly to the list and the newsgroup.

        - -

        Finally, if the newsgroup is moderated, but you want to have - an open posting policy anyway, you should select Open list, - moderated group. The effect of this is to use the normal - Mailman moderation facilities, but to add an Approved - header to all messages that are gatewayed to Usenet.""")), - - ('news_prefix_subject_too', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('Prefix Subject: headers on postings gated to news?'), - _("""Mailman prefixes Subject: headers with - text you can - customize and normally, this prefix shows up in messages - gatewayed to Usenet. You can set this option to No to - disable the prefix on gated messages. Of course, if you turn off - normal Subject: prefixes, they won't be prefixed for - gated messages either.""")), - - _('Mass catch up'), - - ('_mass_catchup', mm_cfg.Toggle, (_('No'), _('Yes')), 0, - _('Should Mailman perform a catchup on the newsgroup?'), - _('''When you tell Mailman to perform a catchup on the newsgroup, - this means that you want to start gating messages to the mailing - list with the next new message found. All earlier messages on - the newsgroup will be ignored. This is as if you were reading - the newsgroup yourself, and you marked all current messages as - read. By catching up, your mailing list members will - not see any of the earlier messages.''')), - - ] - - def _setValue(self, mlist, property, val, doc): - # Watch for the special, immediate action attributes - if property == '_mass_catchup' and val: - mlist.usenet_watermark = None - doc.AddItem(_('Mass catchup completed')) - else: - GUIBase._setValue(self, mlist, property, val, doc) - - def _postValidate(self, mlist, doc): - # Make sure that if we're gating, that the newsgroups and host - # information are not blank. - if mlist.gateway_to_news or mlist.gateway_to_mail: - # BAW: It's too expensive and annoying to ensure that both the - # host is valid and that the newsgroup is a valid n.g. on the - # server. This should be good enough. - if not mlist.nntp_host or not mlist.linked_newsgroup: - doc.addError(_("""You cannot enable gatewaying unless both the - news server field and - the linked - newsgroup fields are filled in.""")) - # And reset these values - mlist.gateway_to_news = 0 - mlist.gateway_to_mail = 0 diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/__init__.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/__init__.py deleted file mode 100644 index 5bf28bc..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Gui/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2001,2002 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -from Archive import Archive -from Autoresponse import Autoresponse -from Bounce import Bounce -from Digest import Digest -from General import General -from Membership import Membership -from NonDigest import NonDigest -from Passwords import Passwords -from Privacy import Privacy -from Topics import Topics -from Usenet import Usenet -from Language import Language -from ContentFilter import ContentFilter - -# Don't export this symbol outside the package -del GUIBase diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/HTMLFormatter.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/HTMLFormatter.py deleted file mode 100644 index 7a5d69d..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/HTMLFormatter.py +++ /dev/null @@ -1,472 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - - -"""Routines for presentation of list-specific HTML text.""" - -import time -import re - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import MemberAdaptor -from Mailman.htmlformat import * - -from Mailman.i18n import _ - - -EMPTYSTRING = '' -COMMASPACE = ', ' - - - -class HTMLFormatter: - def GetMailmanFooter(self): - ownertext = COMMASPACE.join([Utils.ObscureEmail(a, 1) - for a in self.owner]) - # Remove the .Format() when htmlformat conversion is done. - realname = self.real_name - hostname = self.host_name - listinfo_link = Link(self.GetScriptURL('listinfo'), realname).Format() - owner_link = Link('mailto:' + self.GetOwnerEmail(), ownertext).Format() - innertext = _('%(listinfo_link)s list run by %(owner_link)s') - return Container( - '

        ', - #Address( - Container( - innertext, - '
        ', - Link(self.GetScriptURL('admin'), - _('%(realname)s administrative interface')), - _(' (requires authorization)'), - '
        ', - Link(Utils.ScriptURL('listinfo'), - _('Overview of all %(hostname)s mailing lists')), - '

        ', MailmanLogo())).Format() - - def FormatUsers(self, digest, lang=None): - if lang is None: - lang = self.preferred_language - conceal_sub = mm_cfg.ConcealSubscription - people = [] - if digest: - digestmembers = self.getDigestMemberKeys() - for dm in digestmembers: - if not self.getMemberOption(dm, conceal_sub): - people.append(dm) - num_concealed = len(digestmembers) - len(people) - else: - members = self.getRegularMemberKeys() - for m in members: - if not self.getMemberOption(m, conceal_sub): - people.append(m) - num_concealed = len(members) - len(people) - if num_concealed == 1: - concealed = _('(1 private member not shown)') - elif num_concealed > 1: - concealed = _( - '(%(num_concealed)d private members not shown)') - else: - concealed = '' - items = [] - people.sort() - obscure = self.obscure_addresses - for person in people: - id = Utils.ObscureEmail(person) - url = self.GetOptionsURL(person, obscure=obscure) - if obscure: - showing = Utils.ObscureEmail(person, for_text=1) - else: - showing = person - got = Link(url, showing) - if self.getDeliveryStatus(person) <> MemberAdaptor.ENABLED: - got = Italic('(', got, ')') - items.append(got) - # Just return the .Format() so this works until I finish - # converting everything to htmlformat... - return concealed + UnorderedList(*tuple(items)).Format() - - def FormatOptionButton(self, option, value, user): - if option == mm_cfg.DisableDelivery: - optval = self.getDeliveryStatus(user) <> MemberAdaptor.ENABLED - else: - optval = self.getMemberOption(user, option) - if optval == value: - checked = ' checked="checked"' - else: - checked = '' - name = {mm_cfg.DontReceiveOwnPosts : 'dontreceive', - mm_cfg.DisableDelivery : 'disablemail', - mm_cfg.DisableMime : 'mime', - mm_cfg.AcknowledgePosts : 'ackposts', - mm_cfg.Digests : 'digest', - mm_cfg.ConcealSubscription : 'conceal', - mm_cfg.SuppressPasswordReminder : 'remind', - mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic', - mm_cfg.DontReceiveDuplicates : 'nodupes', - }[option] - return '' % ( - name, value, checked) - - def FormatDigestButton(self): - if self.digest_is_default: - checked = ' checked="checked"' - else: - checked = '' - return '' % checked - - def FormatDisabledNotice(self, user): - status = self.getDeliveryStatus(user) - reason = None - info = self.getBounceInfo(user) - if status == MemberAdaptor.BYUSER: - reason = _('; it was disabled by you') - elif status == MemberAdaptor.BYADMIN: - reason = _('; it was disabled by the list administrator') - elif status == MemberAdaptor.BYBOUNCE: - date = time.strftime('%d-%b-%Y', - time.localtime(Utils.midnight(info.date))) - reason = _('''; it was disabled due to excessive bounces. The - last bounce was received on %(date)s''') - elif status == MemberAdaptor.UNKNOWN: - reason = _('; it was disabled for unknown reasons') - if reason: - note = Header(3, _('Your list delivery is currently disabled%(reason)s.')).Format() - link = Link('#mdelivery', _('Mail delivery')).Format() - mailto = Link('mailto:' + self.GetOwnerEmail(), - _('the list administrator')).Format() - note = Div(note, Paragraph(_('''You may have disabled list delivery intentionally, or it may have been triggered by bounces from your email address. In either case, to re-enable delivery, change the %(link)s option below. Contact %(mailto)s if you have any questions or need assistance.'''))).Format(css='class="message warning"') - return note - elif info and info.score > 0: - # Provide information about their current bounce score. We know - # their membership is currently enabled. - score = info.score - total = self.bounce_score_threshold - note = Div(Paragraph(_(''' - We have received some recent bounces from your - address. Your current bounce score is %(score)s out of a - maximum of %(total)s. Please double check that your subscribed - address is correct and that there are no problems with delivery to - this address. Your bounce score will be automatically reset if - the problems are corrected soon. - '''))).Format(css='class="message warning"') - return note - else: - return '' - - def FormatUmbrellaNotice(self, user, type): - addr = self.GetMemberAdminEmail(user) - if self.umbrella_list: - msg = Paragraph(_(''' - You are subscribing to a list of mailing - lists, so the %(type)s notice will be sent to - the admin address for your membership, %(addr)s. - ''')).Format() - return msg - else: - return "" - - def FormatSubscriptionMsg(self): - msg = '' - also = '' - if self.subscribe_policy == 1: - msg += _(''' - You will be sent email requesting confirmation, to - prevent others from gratuitously subscribing you. - ''') - elif self.subscribe_policy == 2: - msg += _(''' - This is a closed list, which means your subscription - will be held for approval. You will be notified of the list - moderator's decision by email. - ''') - also = _('also') - elif self.subscribe_policy == 3: - msg += _(''' - You will be sent email requesting confirmation, to - prevent others from gratuitously subscribing you. Once - confirmation is received, your request will be held for approval - by the list moderator. You will be notified of the moderator's - decision by email. - ''') - also = _("also ") - if msg: - msg += ' ' - if self.private_roster == 1: - msg += _(''' - This is %(also)s a private list, which means that the - list of members is not available to non-members. - ''') - elif self.private_roster: - msg += _(''' - This is %(also)s a hidden list, which means that the - list of members is available only to the list administrator. - ''') - else: - note = _(''' - This is %(also)s a public list, which means that the - list of members list is available to everyone. - ''') - if self.obscure_addresses: - note += _(''' - (But we obscure the addresses so they are not - easily recognizable by spammers). - ''') - msg += Paragraph(note).Format() - - if self.umbrella_list: - sfx = self.umbrella_member_suffix - msg += Paragraph(_(''' - Note that this is an umbrella list, intended to - have only other mailing lists as members. Among other things, - this means that your confirmation request will be sent to the - `%(sfx)s' account for your address. - ''')).Format() - return msg - - def FormatUndigestButton(self): - if self.digest_is_default: - checked = '' - else: - checked = ' checked="checked"' - return '' % checked - - def FormatMimeDigestsButton(self): - if self.mime_is_default_digest: - checked = ' checked="checked"' - else: - checked = '' - return '' % checked - - def FormatPlainDigestsButton(self): - if self.mime_is_default_digest: - checked = '' - else: - checked = ' checked="checked"' - return '' % checked - - def FormatEditingOption(self, lang): - realname = self.real_name - if self.private_roster == 0: - either = _('either ') - else: - either = '' - - # Text used in this section - text = _('''To unsubscribe from %(realname)s, get a password reminder, or change your subscription options %(either)senter your subscription email address:''') - if self.private_roster == 0: - text += Paragraph(_('''... or select your entry from the subscribers list (see above).''')).Format() - - # Build table where texts will be shown. - table = Table() - table.AddRow([text, - TextBox('email', size=30) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - - - table.AddRow([Paragraph(_('''If you leave the field blank, you will be prompted for your email address.''')).Format(css='class="center"') + - Hidden('language', lang).Format() + - SubmitButton('UserOptions', - _('Unsubscribe or edit options')).Format() - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') - return table.Format() - - def RestrictedListMessage(self, which, restriction): - if not restriction: - return '' - elif restriction == 1: - return Paragraph(_('''(%(which)s is only available to the list members.)''')).Format() - else: - return Paragraph(_('''(%(which)s is only available to the list administrator.)''')).Format() - - def FormatRosterOptionForUser(self, lang): - return self.RosterOption(lang).Format() - - def RosterOption(self, lang): - container = Container() - if not self.private_roster: - container.AddItem(Paragraph(_("Click here for the list of ") - + self.real_name - + _(" subscribers: ") - + SubmitButton('SubscriberRoster', - _("Visit Subscriber list"))).Format()) - else: - if self.private_roster == 1: - only = _('members') - whom = _('Address:') - else: - only = _('the list administrator') - whom = _('Admin address:') - - # Solicit the user and password. - table = Table() - container.AddItem(self.RestrictedListMessage(_('The subscribers list'), - self.private_roster)) - container.AddItem(Paragraph(_('Enter your ') - + whom[:-1].lower() - + _(" and password to visit" - " the subscribers list:")).Format()) - table.AddRow([whom, - self.FormatBox('roster-email')]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - table.AddRow([_("Password: "), - self.FormatSecureBox('roster-pw')]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') - table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') - table.AddRow([ - SubmitButton('SubscriberRoster', _("Visit Subscriber list")).Format() + - Hidden('language', lang).Format() - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') - container.AddItem(table) - return container - - def FormatFormStart(self, name, extra=''): - base_url = self.GetScriptURL(name) - if extra: - full_url = "%s/%s" % (base_url, extra) - else: - full_url = base_url - return ('
        ' % full_url) - - def FormatArchiveAnchor(self): - return '' % self.GetBaseArchiveURL() - - def FormatFormEnd(self): - return '' - - def FormatBox(self, name, size=20, value=''): - if isinstance(value, str): - safevalue = Utils.websafe(value) - else: - safevalue = value - return '' % ( - name, size, safevalue) - - def FormatSecureBox(self, name): - return '' % name - - def FormatButton(self, name, text='Submit'): - return '' % (name, text) - - def FormatReminder(self, lang): - if self.send_reminders: - return _('Once a month, your password will be emailed to you as' - ' a reminder.') - return '' - - def ParseTags(self, template, replacements, lang=None): - if lang is None: - charset = 'us-ascii' - else: - charset = Utils.GetCharSet(lang) - text = Utils.maketext(template, raw=1, lang=lang, mlist=self) - parts = re.split('(]*>)', text) - i = 1 - while i < len(parts): - tag = parts[i].lower() - if replacements.has_key(tag): - repl = replacements[tag] - if isinstance(repl, type(u'')): - repl = repl.encode(charset, 'replace') - parts[i] = repl - else: - parts[i] = '' - i = i + 2 - return EMPTYSTRING.join(parts) - - # This needs to wait until after the list is inited, so let's build it - # when it's needed only. - def GetStandardReplacements(self, lang=None): - dmember_len = len(self.getDigestMemberKeys()) - member_len = len(self.getRegularMemberKeys()) - # If only one language is enabled for this mailing list, omit the - # language choice buttons. - if len(self.GetAvailableLanguages()) == 1: - listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) - else: - listlangs = self.GetLangSelectBox(lang).Format() - # If no info is available for the mailing list, omit the info texts. - if self.info: - listabout = '

        ' + self.info.replace('\n', '
        ') + '

        ' - else: - listabout = '' - d = { - '' : self.GetMailmanFooter(), - '' : self.real_name, - '' : self._internal_name, - '' : listabout, - '' : self.FormatFormEnd(), - '' : self.FormatArchiveAnchor(), - '' : '
        ', - '' : self.FormatSubscriptionMsg(), - '' : \ - self.RestrictedListMessage(_('The current archive'), - self.archive_private), - '' : `member_len`, - '' : `dmember_len`, - '' : (`member_len + dmember_len`), - '' : '%s' % self.GetListEmail(), - '' : '%s' % self.GetRequestEmail(), - '' : self.GetOwnerEmail(), - '' : self.FormatReminder(self.preferred_language), - '' : self.host_name, - '' : listlangs, - } - - if mm_cfg.IMAGE_LOGOS: - d[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON - - # To avoid empty tags we redifine description here. - if self.description: - d[''] = Paragraph(self.description).Format() - else: - d[''] = '' - - return d - - def GetAllReplacements(self, lang=None): - """ - returns standard replaces plus formatted user lists in - a dict just like GetStandardReplacements. - """ - if lang is None: - lang = self.preferred_language - d = self.GetStandardReplacements(lang) - d.update({"": self.FormatUsers(0, lang), - "": self.FormatUsers(1, lang)}) - return d - - def GetLangSelectBox(self, lang=None, varname='language'): - if lang is None: - lang = self.preferred_language - # Figure out the available languages - values = self.GetAvailableLanguages() - legend = map(_, map(Utils.GetLanguageDescr, values)) - try: - selected = values.index(lang) - except ValueError: - try: - selected = values.index(self.preferred_language) - except ValueError: - selected = mm_cfg.DEFAULT_SERVER_LANGUAGE - # Return the widget - return SelectOptions(varname, values, legend, selected) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/MailList.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/MailList.py deleted file mode 100644 index e07e23a..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/MailList.py +++ /dev/null @@ -1,1511 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - - -"""The class representing a Mailman mailing list. - -Mixes in many task-specific classes. -""" - -import sys -import os -import time -import marshal -import errno -import re -import shutil -import socket -import urllib -import cPickle - -from cStringIO import StringIO -from UserDict import UserDict -from urlparse import urlparse -from types import * - -import email.Iterators -from email.Utils import getaddresses, formataddr, parseaddr -from email.Header import Header - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import Errors -from Mailman import LockFile -from Mailman.UserDesc import UserDesc - -# base classes -from Mailman.Archiver import Archiver -from Mailman.Autoresponder import Autoresponder -from Mailman.Bouncer import Bouncer -from Mailman.Deliverer import Deliverer -from Mailman.Digester import Digester -from Mailman.GatewayManager import GatewayManager -from Mailman.HTMLFormatter import HTMLFormatter -from Mailman.ListAdmin import ListAdmin -from Mailman.SecurityManager import SecurityManager -from Mailman.TopicMgr import TopicMgr -from Mailman import Pending - -# gui components package -from Mailman import Gui - -# other useful classes -from Mailman import MemberAdaptor -from Mailman.OldStyleMemberships import OldStyleMemberships -from Mailman import Message -from Mailman import Site -from Mailman import i18n -from Mailman.Logging.Syslog import syslog - -_ = i18n._ - -EMPTYSTRING = '' - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -# Use mixins here just to avoid having any one chunk be too large. -class MailList(HTMLFormatter, Deliverer, ListAdmin, - Archiver, Digester, SecurityManager, Bouncer, GatewayManager, - Autoresponder, TopicMgr, Pending.Pending): - - # - # A MailList object's basic Python object model support - # - def __init__(self, name=None, lock=1): - # No timeout by default. If you want to timeout, open the list - # unlocked, then lock explicitly. - # - # Only one level of mixin inheritance allowed - for baseclass in self.__class__.__bases__: - if hasattr(baseclass, '__init__'): - baseclass.__init__(self) - # Initialize volatile attributes - self.InitTempVars(name) - # Default membership adaptor class - self._memberadaptor = OldStyleMemberships(self) - # This extension mechanism allows list-specific overrides of any - # method (well, except __init__(), InitTempVars(), and InitVars() - # I think). Note that fullpath() will return None when we're creating - # the list, which will only happen when name is None. - if name is None: - return - filename = os.path.join(self.fullpath(), 'extend.py') - dict = {} - try: - execfile(filename, dict) - except IOError, e: - # Ignore missing files, but log other errors - if e.errno == errno.ENOENT: - pass - else: - syslog('error', 'IOError reading list extension: %s', e) - else: - func = dict.get('extend') - if func: - func(self) - if lock: - # This will load the database. - self.Lock() - else: - self.Load() - - def __getattr__(self, name): - # Because we're using delegation, we want to be sure that attribute - # access to a delegated member function gets passed to the - # sub-objects. This of course imposes a specific name resolution - # order. - try: - return getattr(self._memberadaptor, name) - except AttributeError: - for guicomponent in self._gui: - try: - return getattr(guicomponent, name) - except AttributeError: - pass - else: - raise AttributeError, name - - def __repr__(self): - if self.Locked(): - status = '(locked)' - else: - status = '(unlocked)' - return '' % ( - self.internal_name(), status, id(self)) - - - # - # Lock management - # - def Lock(self, timeout=0): - self.__lock.lock(timeout) - # Must reload our database for consistency. Watch out for lists that - # don't exist. - try: - self.Load() - except Exception: - self.Unlock() - raise - - def Unlock(self): - self.__lock.unlock(unconditionally=1) - - def Locked(self): - return self.__lock.locked() - - - - # - # Useful accessors - # - def internal_name(self): - return self._internal_name - - def fullpath(self): - return self._full_path - - def getListAddress(self, extra=None): - if extra is None: - return '%s@%s' % (self.internal_name(), self.host_name) - return '%s-%s@%s' % (self.internal_name(), extra, self.host_name) - - # For backwards compatibility - def GetBouncesEmail(self): - return self.getListAddress('bounces') - - def GetOwnerEmail(self): - return self.getListAddress('owner') - - def GetRequestEmail(self, cookie=''): - if mm_cfg.VERP_CONFIRMATIONS and cookie: - return self.GetConfirmEmail(cookie) - else: - return self.getListAddress('request') - - def GetConfirmEmail(self, cookie): - return mm_cfg.VERP_CONFIRM_FORMAT % { - 'addr' : '%s-confirm' % self.internal_name(), - 'cookie': cookie, - } + '@' + self.host_name - - def GetConfirmJoinSubject(self, listname, cookie): - if mm_cfg.VERP_CONFIRMATIONS and cookie: - cset = i18n.get_translation().charset() or \ - Utils.GetCharSet(self.preferred_language) - subj = Header( - _('Your confirmation is required to join the %(listname)s mailing list'), - cset, header_name='subject') - return subj - else: - return 'confirm ' + cookie - - def GetConfirmLeaveSubject(self, listname, cookie): - if mm_cfg.VERP_CONFIRMATIONS and cookie: - cset = i18n.get_translation().charset() or \ - Utils.GetCharSet(self.preferred_language) - subj = Header( - _('Your confirmation is required to leave the %(listname)s mailing list'), - cset, header_name='subject') - return subj - else: - return 'confirm ' + cookie - - def GetListEmail(self): - return self.getListAddress() - - def GetMemberAdminEmail(self, member): - """Usually the member addr, but modified for umbrella lists. - - Umbrella lists have other mailing lists as members, and so admin stuff - like confirmation requests and passwords must not be sent to the - member addresses - the sublists - but rather to the administrators of - the sublists. This routine picks the right address, considering - regular member address to be their own administrative addresses. - - """ - if not self.umbrella_list: - return member - else: - acct, host = tuple(member.split('@')) - return "%s%s@%s" % (acct, self.umbrella_member_suffix, host) - - def GetScriptURL(self, scriptname, absolute=0): - return Utils.ScriptURL(scriptname, self.web_page_url, absolute) + \ - '/' + self.internal_name() - - def GetOptionsURL(self, user, obscure=0, absolute=0): - url = self.GetScriptURL('options', absolute) - if obscure: - user = Utils.ObscureEmail(user) - return '%s/%s' % (url, urllib.quote(user.lower())) - - - # - # Instance and subcomponent initialization - # - def InitTempVars(self, name): - """Set transient variables of this and inherited classes.""" - # The timestamp is set whenever we load the state from disk. If our - # timestamp is newer than the modtime of the config.pck file, we don't - # need to reload, otherwise... we do. - self.__timestamp = 0 - self.__lock = LockFile.LockFile( - os.path.join(mm_cfg.LOCK_DIR, name or '') + '.lock', - # TBD: is this a good choice of lifetime? - lifetime = mm_cfg.LIST_LOCK_LIFETIME, - withlogging = mm_cfg.LIST_LOCK_DEBUGGING) - self._internal_name = name - if name: - self._full_path = Site.get_listpath(name) - else: - self._full_path = '' - # Only one level of mixin inheritance allowed - for baseclass in self.__class__.__bases__: - if hasattr(baseclass, 'InitTempVars'): - baseclass.InitTempVars(self) - # Now, initialize our gui components - self._gui = [] - for component in dir(Gui): - if component.startswith('_'): - continue - self._gui.append(getattr(Gui, component)()) - - def InitVars(self, name=None, admin='', crypted_password=''): - """Assign default values - some will be overriden by stored state.""" - # Non-configurable list info - if name: - self._internal_name = name - - # When was the list created? - self.created_at = time.time() - - # Must save this state, even though it isn't configurable - self.volume = 1 - self.members = {} # self.digest_members is initted in mm_digest - self.data_version = mm_cfg.DATA_FILE_VERSION - self.last_post_time = 0 - - self.post_id = 1. # A float so it never has a chance to overflow. - self.user_options = {} - self.language = {} - self.usernames = {} - self.passwords = {} - self.new_member_options = mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS - - # This stuff is configurable - self.respond_to_post_requests = 1 - self.advertised = mm_cfg.DEFAULT_LIST_ADVERTISED - self.max_num_recipients = mm_cfg.DEFAULT_MAX_NUM_RECIPIENTS - self.max_message_size = mm_cfg.DEFAULT_MAX_MESSAGE_SIZE - # See the note in Defaults.py concerning DEFAULT_HOST_NAME - # vs. DEFAULT_EMAIL_HOST. - self.host_name = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST - self.web_page_url = ( - mm_cfg.DEFAULT_URL or - mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST) - self.owner = [admin] - self.moderator = [] - self.reply_goes_to_list = mm_cfg.DEFAULT_REPLY_GOES_TO_LIST - self.reply_to_address = '' - self.first_strip_reply_to = mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO - self.admin_immed_notify = mm_cfg.DEFAULT_ADMIN_IMMED_NOTIFY - self.admin_notify_mchanges = \ - mm_cfg.DEFAULT_ADMIN_NOTIFY_MCHANGES - self.require_explicit_destination = \ - mm_cfg.DEFAULT_REQUIRE_EXPLICIT_DESTINATION - self.acceptable_aliases = mm_cfg.DEFAULT_ACCEPTABLE_ALIASES - self.umbrella_list = mm_cfg.DEFAULT_UMBRELLA_LIST - self.umbrella_member_suffix = \ - mm_cfg.DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX - self.send_reminders = mm_cfg.DEFAULT_SEND_REMINDERS - self.send_welcome_msg = mm_cfg.DEFAULT_SEND_WELCOME_MSG - self.send_goodbye_msg = mm_cfg.DEFAULT_SEND_GOODBYE_MSG - self.bounce_matching_headers = \ - mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS - self.header_filter_rules = [] - self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST - internalname = self.internal_name() - self.real_name = internalname[0].upper() + internalname[1:] - self.description = '' - self.info = '' - self.welcome_msg = '' - self.goodbye_msg = '' - self.subscribe_policy = mm_cfg.DEFAULT_SUBSCRIBE_POLICY - self.unsubscribe_policy = mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY - self.private_roster = mm_cfg.DEFAULT_PRIVATE_ROSTER - self.obscure_addresses = mm_cfg.DEFAULT_OBSCURE_ADDRESSES - self.admin_member_chunksize = mm_cfg.DEFAULT_ADMIN_MEMBER_CHUNKSIZE - self.administrivia = mm_cfg.DEFAULT_ADMINISTRIVIA - self.preferred_language = mm_cfg.DEFAULT_SERVER_LANGUAGE - self.available_languages = [] - self.include_rfc2369_headers = 1 - self.include_list_post_header = 1 - self.filter_mime_types = mm_cfg.DEFAULT_FILTER_MIME_TYPES - self.pass_mime_types = mm_cfg.DEFAULT_PASS_MIME_TYPES - self.filter_filename_extensions = \ - mm_cfg.DEFAULT_FILTER_FILENAME_EXTENSIONS - self.pass_filename_extensions = mm_cfg.DEFAULT_PASS_FILENAME_EXTENSIONS - self.filter_content = mm_cfg.DEFAULT_FILTER_CONTENT - self.collapse_alternatives = mm_cfg.DEFAULT_COLLAPSE_ALTERNATIVES - self.convert_html_to_plaintext = \ - mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT - self.filter_action = mm_cfg.DEFAULT_FILTER_ACTION - # Analogs to these are initted in Digester.InitVars - self.nondigestable = mm_cfg.DEFAULT_NONDIGESTABLE - self.personalize = 0 - # New sender-centric moderation (privacy) options - self.default_member_moderation = \ - mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION - # Emergency moderation bit - self.emergency = 0 - # This really ought to default to mm_cfg.HOLD, but that doesn't work - # with the current GUI description model. So, 0==Hold, 1==Reject, - # 2==Discard - self.member_moderation_action = 0 - self.member_moderation_notice = '' - self.accept_these_nonmembers = [] - self.hold_these_nonmembers = [] - self.reject_these_nonmembers = [] - self.discard_these_nonmembers = [] - self.forward_auto_discards = mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS - self.generic_nonmember_action = mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION - self.nonmember_rejection_notice = '' - # Ban lists - self.ban_list = [] - # BAW: This should really be set in SecurityManager.InitVars() - self.password = crypted_password - # Max autoresponses per day. A mapping between addresses and a - # 2-tuple of the date of the last autoresponse and the number of - # autoresponses sent on that date. - self.hold_and_cmd_autoresponses = {} - # Only one level of mixin inheritance allowed - for baseclass in self.__class__.__bases__: - if hasattr(baseclass, 'InitVars'): - baseclass.InitVars(self) - - # These need to come near the bottom because they're dependent on - # other settings. - self.subject_prefix = mm_cfg.DEFAULT_SUBJECT_PREFIX % self.__dict__ - self.msg_header = mm_cfg.DEFAULT_MSG_HEADER - self.msg_footer = mm_cfg.DEFAULT_MSG_FOOTER - # Set this to Never if the list's preferred language uses us-ascii, - # otherwise set it to As Needed - if Utils.GetCharSet(self.preferred_language) == 'us-ascii': - self.encode_ascii_prefixes = 0 - else: - self.encode_ascii_prefixes = 2 - # scrub regular delivery - self.scrub_nondigest = mm_cfg.DEFAULT_SCRUB_NONDIGEST - # automatic discarding - self.max_days_to_hold = mm_cfg.DEFAULT_MAX_DAYS_TO_HOLD - - - # - # Web API support via administrative categories - # - def GetConfigCategories(self): - class CategoryDict(UserDict): - def __init__(self): - UserDict.__init__(self) - self.keysinorder = mm_cfg.ADMIN_CATEGORIES[:] - def keys(self): - return self.keysinorder - def items(self): - items = [] - for k in mm_cfg.ADMIN_CATEGORIES: - items.append((k, self.data[k])) - return items - def values(self): - values = [] - for k in mm_cfg.ADMIN_CATEGORIES: - values.append(self.data[k]) - return values - - categories = CategoryDict() - # Only one level of mixin inheritance allowed - for gui in self._gui: - k, v = gui.GetConfigCategory() - categories[k] = (v, gui) - return categories - - def GetConfigSubCategories(self, category): - for gui in self._gui: - if hasattr(gui, 'GetConfigSubCategories'): - # Return the first one that knows about the given subcategory - subcat = gui.GetConfigSubCategories(category) - if subcat is not None: - return subcat - return None - - def GetConfigInfo(self, category, subcat=None): - for gui in self._gui: - if hasattr(gui, 'GetConfigInfo'): - value = gui.GetConfigInfo(self, category, subcat) - if value: - return value - - - # - # List creation - # - def Create(self, name, admin, crypted_password, - langs=None, emailhost=None): - if Utils.list_exists(name): - raise Errors.MMListAlreadyExistsError, name - # Validate what will be the list's posting address. If that's - # invalid, we don't want to create the mailing list. The hostname - # part doesn't really matter, since that better already be valid. - # However, most scripts already catch MMBadEmailError as exceptions on - # the admin's email address, so transform the exception. - if emailhost is None: - emailhost = mm_cfg.DEFAULT_EMAIL_HOST - postingaddr = '%s@%s' % (name, emailhost) - try: - Utils.ValidateEmail(postingaddr) - except Errors.MMBadEmailError: - raise Errors.BadListNameError, postingaddr - # Validate the admin's email address - Utils.ValidateEmail(admin) - self._internal_name = name - self._full_path = Site.get_listpath(name, create=1) - # Don't use Lock() since that tries to load the non-existant config.pck - self.__lock.lock() - self.InitVars(name, admin, crypted_password) - self.CheckValues() - if langs is None: - self.available_languages = [self.preferred_language] - else: - self.available_languages = langs - - - - # - # Database and filesystem I/O - # - def __save(self, dict): - # Save the file as a binary pickle, and rotate the old version to a - # backup file. We must guarantee that config.pck is always valid so - # we never rotate unless the we've successfully written the temp file. - # We use pickle now because marshal is not guaranteed to be compatible - # between Python versions. - fname = os.path.join(self.fullpath(), 'config.pck') - fname_tmp = fname + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) - fname_last = fname + '.last' - fp = None - try: - fp = open(fname_tmp, 'w') - # Use a binary format... it's more efficient. - cPickle.dump(dict, fp, 1) - fp.flush() - if mm_cfg.SYNC_AFTER_WRITE: - os.fsync(fp.fileno()) - fp.close() - except IOError, e: - syslog('error', - 'Failed config.pck write, retaining old state.\n%s', e) - if fp is not None: - os.unlink(fname_tmp) - raise - # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation - # as safely as possible. - try: - # might not exist yet - os.unlink(fname_last) - except OSError, e: - if e.errno <> errno.ENOENT: raise - try: - # might not exist yet - os.link(fname, fname_last) - except OSError, e: - if e.errno <> errno.ENOENT: raise - os.rename(fname_tmp, fname) - # Reset the timestamp - self.__timestamp = os.path.getmtime(fname) - - def Save(self): - # Refresh the lock, just to let other processes know we're still - # interested in it. This will raise a NotLockedError if we don't have - # the lock (which is a serious problem!). TBD: do we need to be more - # defensive? - self.__lock.refresh() - # copy all public attributes to serializable dictionary - dict = {} - for key, value in self.__dict__.items(): - if key[0] == '_' or type(value) is MethodType: - continue - dict[key] = value - # Make config.pck unreadable by `other', as it contains all the - # list members' passwords (in clear text). - omask = os.umask(007) - try: - self.__save(dict) - finally: - os.umask(omask) - self.SaveRequestsDb() - self.CheckHTMLArchiveDir() - - def __load(self, dbfile): - # Attempt to load and unserialize the specified database file. This - # could actually be a config.db (for pre-2.1alpha3) or config.pck, - # i.e. a marshal or a binary pickle. Actually, it could also be a - # .last backup file if the primary storage file was corrupt. The - # decision on whether to unpickle or unmarshal is based on the file - # extension, but we always save it using pickle (since only it, and - # not marshal is guaranteed to be compatible across Python versions). - # - # On success return a 2-tuple of (dictionary, None). On error, return - # a 2-tuple of the form (None, errorobj). - if dbfile.endswith('.db') or dbfile.endswith('.db.last'): - loadfunc = marshal.load - elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): - loadfunc = cPickle.load - else: - assert 0, 'Bad database file name' - try: - # Check the mod time of the file first. If it matches our - # timestamp, then the state hasn't change since the last time we - # loaded it. Otherwise open the file for loading, below. If the - # file doesn't exist, we'll get an EnvironmentError with errno set - # to ENOENT (EnvironmentError is the base class of IOError and - # OSError). - mtime = os.path.getmtime(dbfile) - if mtime <= self.__timestamp: - # File is not newer - return None, None - fp = open(dbfile) - except EnvironmentError, e: - if e.errno <> errno.ENOENT: raise - # The file doesn't exist yet - return None, e - try: - try: - dict = loadfunc(fp) - if type(dict) <> DictType: - return None, 'Load() expected to return a dictionary' - except (EOFError, ValueError, TypeError, MemoryError, - cPickle.PicklingError, cPickle.UnpicklingError), e: - return None, e - finally: - fp.close() - # Update timestamp - self.__timestamp = mtime - return dict, None - - def Load(self, check_version=True): - if not Utils.list_exists(self.internal_name()): - raise Errors.MMUnknownListError - # We first try to load config.pck, which contains the up-to-date - # version of the database. If that fails, perhaps because it's - # corrupted or missing, we'll try to load the backup file - # config.pck.last. - # - # Should both of those fail, we'll look for config.db and - # config.db.last for backwards compatibility with pre-2.1alpha3 - pfile = os.path.join(self.fullpath(), 'config.pck') - plast = pfile + '.last' - dfile = os.path.join(self.fullpath(), 'config.db') - dlast = dfile + '.last' - for file in (pfile, plast, dfile, dlast): - dict, e = self.__load(file) - if dict is None: - if e is not None: - # Had problems with this file; log it and try the next one. - syslog('error', "couldn't load config file %s\n%s", - file, e) - else: - # We already have the most up-to-date state - return - else: - break - else: - # Nothing worked, so we have to give up - syslog('error', 'All %s fallbacks were corrupt, giving up', - self.internal_name()) - raise Errors.MMCorruptListDatabaseError, e - # Now, if we didn't end up using the primary database file, we want to - # copy the fallback into the primary so that the logic in Save() will - # still work. For giggles, we'll copy it to a safety backup. Note we - # MUST do this with the underlying list lock acquired. - if file == plast or file == dlast: - syslog('error', 'fixing corrupt config file, using: %s', file) - unlock = True - try: - try: - self.__lock.lock() - except LockFile.AlreadyLockedError: - unlock = False - self.__fix_corrupt_pckfile(file, pfile, plast, dfile, dlast) - finally: - if unlock: - self.__lock.unlock() - # Copy the loaded dictionary into the attributes of the current - # mailing list object, then run sanity check on the data. - self.__dict__.update(dict) - if check_version: - self.CheckVersion(dict) - self.CheckValues() - - def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast): - if file == plast: - # Move aside any existing pickle file and delete any existing - # safety file. This avoids EPERM errors inside the shutil.copy() - # calls if those files exist with different ownership. - try: - os.rename(pfile, pfile + '.corrupt') - except OSError, e: - if e.errno <> errno.ENOENT: raise - try: - os.remove(pfile + '.safety') - except OSError, e: - if e.errno <> errno.ENOENT: raise - shutil.copy(file, pfile) - shutil.copy(file, pfile + '.safety') - elif file == dlast: - # Move aside any existing marshal file and delete any existing - # safety file. This avoids EPERM errors inside the shutil.copy() - # calls if those files exist with different ownership. - try: - os.rename(dfile, dfile + '.corrupt') - except OSError, e: - if e.errno <> errno.ENOENT: raise - try: - os.remove(dfile + '.safety') - except OSError, e: - if e.errno <> errno.ENOENT: raise - shutil.copy(file, dfile) - shutil.copy(file, dfile + '.safety') - - - # - # Sanity checks - # - def CheckVersion(self, stored_state): - """Auto-update schema if necessary.""" - if self.data_version >= mm_cfg.DATA_FILE_VERSION: - return - # Initialize any new variables - self.InitVars() - # Then reload the database (but don't recurse). Force a reload even - # if we have the most up-to-date state. - self.__timestamp = 0 - self.Load(check_version=0) - # We must hold the list lock in order to update the schema - waslocked = self.Locked() - if not waslocked: - self.Lock() - try: - from versions import Update - Update(self, stored_state) - self.data_version = mm_cfg.DATA_FILE_VERSION - self.Save() - finally: - if not waslocked: - self.Unlock() - - def CheckValues(self): - """Normalize selected values to known formats.""" - if '' in urlparse(self.web_page_url)[:2]: - # Either the "scheme" or the "network location" part of the parsed - # URL is empty; substitute faulty value with (hopefully sane) - # default. Note that DEFAULT_URL is obsolete. - self.web_page_url = ( - mm_cfg.DEFAULT_URL or - mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST) - if self.web_page_url and self.web_page_url[-1] <> '/': - self.web_page_url = self.web_page_url + '/' - # Legacy reply_to_address could be an illegal value. We now verify - # upon setting and don't check it at the point of use. - try: - if self.reply_to_address.strip() and self.reply_goes_to_list: - Utils.ValidateEmail(self.reply_to_address) - except Errors.EmailAddressError: - syslog('error', 'Bad reply_to_address "%s" cleared for list: %s', - self.reply_to_address, self.internal_name()) - self.reply_to_address = '' - self.reply_goes_to_list = 0 - # Legacy topics may have bad regular expressions in their patterns - goodtopics = [] - for name, pattern, desc, emptyflag in self.topics: - try: - re.compile(pattern) - except (re.error, TypeError): - syslog('error', 'Bad topic pattern "%s" for list: %s', - pattern, self.internal_name()) - else: - goodtopics.append((name, pattern, desc, emptyflag)) - self.topics = goodtopics - - - # - # Membership management front-ends and assertion checks - # - def InviteNewMember(self, userdesc, text=''): - """Invite a new member to the list. - - This is done by creating a subscription pending for the user, and then - crafting a message to the member informing them of the invitation. - """ - invitee = userdesc.address - Utils.ValidateEmail(invitee) - # check for banned address - pattern = self.GetBannedPattern(invitee) - if pattern: - raise Errors.MembershipIsBanned, pattern - # Hack alert! Squirrel away a flag that only invitations have, so - # that we can do something slightly different when an invitation - # subscription is confirmed. In those cases, we don't need further - # admin approval, even if the list is so configured. The flag is the - # list name to prevent invitees from cross-subscribing. - userdesc.invitation = self.internal_name() - cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) - requestaddr = self.getListAddress('request') - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - cookie) - listname = self.real_name - text += Utils.maketext( - 'invite.txt', - {'email' : invitee, - 'listname' : listname, - 'hostname' : self.host_name, - 'confirmurl' : confirmurl, - 'requestaddr': requestaddr, - 'cookie' : cookie, - 'listowner' : self.GetOwnerEmail(), - }, mlist=self) - sender = self.GetRequestEmail(cookie) - msg = Message.UserNotification( - invitee, sender, - text=text, lang=self.preferred_language) - subj = self.GetConfirmJoinSubject(listname, cookie) - del msg['subject'] - msg['Subject'] = subj - msg.send(self) - - def AddMember(self, userdesc, remote=None): - """Front end to member subscription. - - This method enforces subscription policy, validates values, sends - notifications, and any other grunt work involved in subscribing a - user. It eventually calls ApprovedAddMember() to do the actual work - of subscribing the user. - - userdesc is an instance with the following public attributes: - - address -- the unvalidated email address of the member - fullname -- the member's full name (i.e. John Smith) - digest -- a flag indicating whether the user wants digests or not - language -- the requested default language for the user - password -- the user's password - - Other attributes may be defined later. Only address is required; the - others all have defaults (fullname='', digests=0, language=list's - preferred language, password=generated). - - remote is a string which describes where this add request came from. - """ - assert self.Locked() - # Suck values out of userdesc, apply defaults, and reset the userdesc - # attributes (for passing on to ApprovedAddMember()). Lowercase the - # addr's domain part. - email = Utils.LCDomain(userdesc.address) - name = getattr(userdesc, 'fullname', '') - lang = getattr(userdesc, 'language', self.preferred_language) - digest = getattr(userdesc, 'digest', None) - password = getattr(userdesc, 'password', Utils.MakeRandomPassword()) - if digest is None: - if self.nondigestable: - digest = 0 - else: - digest = 1 - # Validate the e-mail address to some degree. - Utils.ValidateEmail(email) - if self.isMember(email): - raise Errors.MMAlreadyAMember, email - if email.lower() == self.GetListEmail().lower(): - # Trying to subscribe the list to itself! - raise Errors.MMBadEmailError - realname = self.real_name - # Is the subscribing address banned from this list? - pattern = self.GetBannedPattern(email) - if pattern: - syslog('vette', '%s banned subscription: %s (matched: %s)', - realname, email, pattern) - raise Errors.MembershipIsBanned, pattern - # Sanity check the digest flag - if digest and not self.digestable: - raise Errors.MMCantDigestError - elif not digest and not self.nondigestable: - raise Errors.MMMustDigestError - - userdesc.address = email - userdesc.fullname = name - userdesc.digest = digest - userdesc.language = lang - userdesc.password = password - - # Apply the list's subscription policy. 0 means open subscriptions; 1 - # means the user must confirm; 2 means the admin must approve; 3 means - # the user must confirm and then the admin must approve - if self.subscribe_policy == 0: - self.ApprovedAddMember(userdesc, whence=remote or '') - elif self.subscribe_policy == 1 or self.subscribe_policy == 3: - # User confirmation required. BAW: this should probably just - # accept a userdesc instance. - cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) - # Send the user the confirmation mailback - if remote is None: - by = remote = '' - else: - by = ' ' + remote - remote = _(' from %(remote)s') - - recipient = self.GetMemberAdminEmail(email) - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - cookie) - text = Utils.maketext( - 'verify.txt', - {'email' : email, - 'listaddr' : self.GetListEmail(), - 'listname' : realname, - 'cookie' : cookie, - 'requestaddr' : self.getListAddress('request'), - 'remote' : remote, - 'listadmin' : self.GetOwnerEmail(), - 'confirmurl' : confirmurl, - }, lang=lang, mlist=self) - msg = Message.UserNotification( - recipient, self.GetRequestEmail(cookie), - text=text, lang=lang) - # BAW: See ChangeMemberAddress() for why we do it this way... - del msg['subject'] - msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie) - msg['Reply-To'] = self.GetRequestEmail(cookie) - msg.send(self) - who = formataddr((name, email)) - syslog('subscribe', '%s: pending %s %s', - self.internal_name(), who, by) - raise Errors.MMSubscribeNeedsConfirmation - else: - # Subscription approval is required. Add this entry to the admin - # requests database. BAW: this should probably take a userdesc - # just like above. - self.HoldSubscription(email, name, password, digest, lang) - raise Errors.MMNeedApproval, _( - 'subscriptions to %(realname)s require moderator approval') - - def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', - whence=''): - """Add a member right now. - - The member's subscription must be approved by what ever policy the - list enforces. - - userdesc is as above in AddMember(). - - ack is a flag that specifies whether the user should get an - acknowledgement of their being subscribed. Default is to use the - list's default flag value. - - admin_notif is a flag that specifies whether the list owner should get - an acknowledgement of this subscription. Default is to use the list's - default flag value. - """ - assert self.Locked() - # Set up default flag values - if ack is None: - ack = self.send_welcome_msg - if admin_notif is None: - admin_notif = self.admin_notify_mchanges - # Suck values out of userdesc, and apply defaults. - email = Utils.LCDomain(userdesc.address) - name = getattr(userdesc, 'fullname', '') - lang = getattr(userdesc, 'language', self.preferred_language) - digest = getattr(userdesc, 'digest', None) - password = getattr(userdesc, 'password', Utils.MakeRandomPassword()) - if digest is None: - if self.nondigestable: - digest = 0 - else: - digest = 1 - # Let's be extra cautious - Utils.ValidateEmail(email) - if self.isMember(email): - raise Errors.MMAlreadyAMember, email - # Check for banned address here too for admin mass subscribes - # and confirmations. - pattern = self.GetBannedPattern(email) - if pattern: - raise Errors.MembershipIsBanned, pattern - # Do the actual addition - self.addNewMember(email, realname=name, digest=digest, - password=password, language=lang) - self.setMemberOption(email, mm_cfg.DisableMime, - 1 - self.mime_is_default_digest) - self.setMemberOption(email, mm_cfg.Moderate, - self.default_member_moderation) - # Now send and log results - if digest: - kind = ' (digest)' - else: - kind = '' - syslog('subscribe', '%s: new%s %s, %s', self.internal_name(), - kind, formataddr((name, email)), whence) - if ack: - self.SendSubscribeAck(email, self.getMemberPassword(email), - digest, text) - if admin_notif: - lang = self.preferred_language - otrans = i18n.get_translation() - i18n.set_language(lang) - try: - realname = self.real_name - subject = _('%(realname)s subscription notification') - finally: - i18n.set_translation(otrans) - if isinstance(name, UnicodeType): - name = name.encode(Utils.GetCharSet(lang), 'replace') - text = Utils.maketext( - "adminsubscribeack.txt", - {"listname" : realname, - "member" : formataddr((name, email)), - }, mlist=self) - msg = Message.OwnerNotification(self, subject, text) - msg.send(self) - - def DeleteMember(self, name, whence=None, admin_notif=None, userack=True): - realname, email = parseaddr(name) - if self.unsubscribe_policy == 0: - self.ApprovedDeleteMember(name, whence, admin_notif, userack) - else: - self.HoldUnsubscription(email) - raise Errors.MMNeedApproval, _( - 'unsubscriptions require moderator approval') - - def ApprovedDeleteMember(self, name, whence=None, - admin_notif=None, userack=None): - if userack is None: - userack = self.send_goodbye_msg - if admin_notif is None: - admin_notif = self.admin_notify_mchanges - # Delete a member, for which we know the approval has been made - fullname, emailaddr = parseaddr(name) - userlang = self.getMemberLanguage(emailaddr) - # Remove the member - self.removeMember(emailaddr) - # And send an acknowledgement to the user... - if userack: - self.SendUnsubscribeAck(emailaddr, userlang) - # ...and to the administrator - if admin_notif: - realname = self.real_name - subject = _('%(realname)s unsubscribe notification') - text = Utils.maketext( - 'adminunsubscribeack.txt', - {'member' : name, - 'listname': self.real_name, - }, mlist=self) - msg = Message.OwnerNotification(self, subject, text) - msg.send(self) - if whence: - whence = "; %s" % whence - else: - whence = "" - syslog('subscribe', '%s: deleted %s%s', - self.internal_name(), name, whence) - - def ChangeMemberName(self, addr, name, globally): - self.setMemberName(addr, name) - if not globally: - return - for listname in Utils.list_names(): - # Don't bother with ourselves - if listname == self.internal_name(): - continue - mlist = MailList(listname, lock=0) - if mlist.host_name <> self.host_name: - continue - if not mlist.isMember(addr): - continue - mlist.Lock() - try: - mlist.setMemberName(addr, name) - mlist.Save() - finally: - mlist.Unlock() - - def ChangeMemberAddress(self, oldaddr, newaddr, globally): - # Changing a member address consists of verifying the new address, - # making sure the new address isn't already a member, and optionally - # going through the confirmation process. - # - # Most of these checks are copied from AddMember - newaddr = Utils.LCDomain(newaddr) - Utils.ValidateEmail(newaddr) - # Raise an exception if this email address is already a member of the - # list, but only if the new address is the same case-wise as the old - # address and we're not doing a global change. - if not globally and newaddr == oldaddr and self.isMember(newaddr): - raise Errors.MMAlreadyAMember - if newaddr == self.GetListEmail().lower(): - raise Errors.MMBadEmailError - realname = self.real_name - # Don't allow changing to a banned address. MAS: maybe we should - # unsubscribe the oldaddr too just for trying, but that's probably - # too harsh. - pattern = self.GetBannedPattern(newaddr) - if pattern: - syslog('vette', - '%s banned address change: %s -> %s (matched: %s)', - realname, oldaddr, newaddr, pattern) - raise Errors.MembershipIsBanned, pattern - # Pend the subscription change - cookie = self.pend_new(Pending.CHANGE_OF_ADDRESS, - oldaddr, newaddr, globally) - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - cookie) - lang = self.getMemberLanguage(oldaddr) - text = Utils.maketext( - 'verify.txt', - {'email' : newaddr, - 'listaddr' : self.GetListEmail(), - 'listname' : realname, - 'cookie' : cookie, - 'requestaddr': self.getListAddress('request'), - 'remote' : '', - 'listadmin' : self.GetOwnerEmail(), - 'confirmurl' : confirmurl, - }, lang=lang, mlist=self) - # BAW: We don't pass the Subject: into the UserNotification - # constructor because it will encode it in the charset of the language - # being used. For non-us-ascii charsets, this means it will probably - # quopri quote it, and thus replies will also be quopri encoded. But - # CommandRunner doesn't yet grok such headers. So, just set the - # Subject: in a separate step, although we have to delete the one - # UserNotification adds. - msg = Message.UserNotification( - newaddr, self.GetRequestEmail(cookie), - text=text, lang=lang) - del msg['subject'] - msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie) - msg['Reply-To'] = self.GetRequestEmail(cookie) - msg.send(self) - - def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally): - # Check here for banned address in case address was banned after - # confirmation was mailed. MAS: If it's global change should we just - # skip this list and proceed to the others? For now we'll throw the - # exception. - pattern = self.GetBannedPattern(newaddr) - if pattern: - raise Errors.MembershipIsBanned, pattern - # It's possible they were a member of this list, but choose to change - # their membership globally. In that case, we simply remove the old - # address. - if self.getMemberCPAddress(oldaddr) == newaddr: - self.removeMember(oldaddr) - else: - self.changeMemberAddress(oldaddr, newaddr) - # If globally is true, then we also include every list for which - # oldaddr is a member. - if not globally: - return - for listname in Utils.list_names(): - # Don't bother with ourselves - if listname == self.internal_name(): - continue - mlist = MailList(listname, lock=0) - if mlist.host_name <> self.host_name: - continue - if not mlist.isMember(oldaddr): - continue - # If new address is banned from this list, just skip it. - if mlist.GetBannedPattern(newaddr): - continue - mlist.Lock() - try: - # Same logic as above, re newaddr is already a member - if mlist.getMemberCPAddress(oldaddr) == newaddr: - mlist.removeMember(oldaddr) - else: - mlist.changeMemberAddress(oldaddr, newaddr) - mlist.Save() - finally: - mlist.Unlock() - - - # - # Confirmation processing - # - def ProcessConfirmation(self, cookie, context=None): - rec = self.pend_confirm(cookie) - if rec is None: - raise Errors.MMBadConfirmation, 'No cookie record for %s' % cookie - try: - op = rec[0] - data = rec[1:] - except ValueError: - raise Errors.MMBadConfirmation, 'op-less data %s' % (rec,) - if op == Pending.SUBSCRIPTION: - whence = 'via email confirmation' - try: - userdesc = data[0] - # If confirmation comes from the web, context should be a - # UserDesc instance which contains overrides of the original - # subscription information. If it comes from email, then - # context is a Message and isn't relevant, so ignore it. - if isinstance(context, UserDesc): - userdesc += context - whence = 'via web confirmation' - addr = userdesc.address - fullname = userdesc.fullname - password = userdesc.password - digest = userdesc.digest - lang = userdesc.language - except ValueError: - raise Errors.MMBadConfirmation, 'bad subscr data %s' % (data,) - # Hack alert! Was this a confirmation of an invitation? - invitation = getattr(userdesc, 'invitation', False) - # We check for both 2 (approval required) and 3 (confirm + - # approval) because the policy could have been changed in the - # middle of the confirmation dance. - if invitation: - if invitation <> self.internal_name(): - # Not cool. The invitee was trying to subscribe to a - # different list than they were invited to. Alert both - # list administrators. - self.SendHostileSubscriptionNotice(invitation, addr) - raise Errors.HostileSubscriptionError - elif self.subscribe_policy in (2, 3): - self.HoldSubscription(addr, fullname, password, digest, lang) - name = self.real_name - raise Errors.MMNeedApproval, _( - 'subscriptions to %(name)s require administrator approval') - self.ApprovedAddMember(userdesc, whence=whence) - return op, addr, password, digest, lang - elif op == Pending.UNSUBSCRIPTION: - addr = data[0] - # Log file messages don't need to be i18n'd - if isinstance(context, Message.Message): - whence = 'email confirmation' - else: - whence = 'web confirmation' - # Can raise NotAMemberError if they unsub'd via other means - self.ApprovedDeleteMember(addr, whence=whence) - return op, addr - elif op == Pending.CHANGE_OF_ADDRESS: - oldaddr, newaddr, globally = data - self.ApprovedChangeMemberAddress(oldaddr, newaddr, globally) - return op, oldaddr, newaddr - elif op == Pending.HELD_MESSAGE: - id = data[0] - approved = None - # Confirmation should be coming from email, where context should - # be the confirming message. If the message does not have an - # Approved: header, this is a discard. If it has an Approved: - # header that does not match the list password, then we'll notify - # the list administrator that they used the wrong password. - # Otherwise it's an approval. - if isinstance(context, Message.Message): - # See if it's got an Approved: header, either in the headers, - # or in the first text/plain section of the response. For - # robustness, we'll accept Approve: as well. - approved = context.get('Approved', context.get('Approve')) - if not approved: - try: - subpart = list(email.Iterators.typed_subpart_iterator( - context, 'text', 'plain'))[0] - except IndexError: - subpart = None - if subpart: - s = StringIO(subpart.get_payload()) - while True: - line = s.readline() - if not line: - break - if not line.strip(): - continue - i = line.find(':') - if i > 0: - if (line[:i].lower() == 'approve' or - line[:i].lower() == 'approved'): - # then - approved = line[i+1:].strip() - break - # Is there an approved header? - if approved is not None: - # Does it match the list password? Note that we purposefully - # do not allow the site password here. - if self.Authenticate([mm_cfg.AuthListAdmin, - mm_cfg.AuthListModerator], - approved) <> mm_cfg.UnAuthorized: - action = mm_cfg.APPROVE - else: - # The password didn't match. Re-pend the message and - # inform the list moderators about the problem. - self.pend_repend(cookie, rec) - raise Errors.MMBadPasswordError - else: - action = mm_cfg.DISCARD - try: - self.HandleRequest(id, action) - except KeyError: - # Most likely because the message has already been disposed of - # via the admindb page. - syslog('error', 'Could not process HELD_MESSAGE: %s', id) - return (op,) - elif op == Pending.RE_ENABLE: - member = data[1] - self.setDeliveryStatus(member, MemberAdaptor.ENABLED) - return op, member - else: - assert 0, 'Bad op: %s' % op - - def ConfirmUnsubscription(self, addr, lang=None, remote=None): - if lang is None: - lang = self.getMemberLanguage(addr) - cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - cookie) - realname = self.real_name - if remote is not None: - by = " " + remote - remote = _(" from %(remote)s") - else: - by = "" - remote = "" - text = Utils.maketext( - 'unsub.txt', - {'email' : addr, - 'listaddr' : self.GetListEmail(), - 'listname' : realname, - 'cookie' : cookie, - 'requestaddr' : self.getListAddress('request'), - 'remote' : remote, - 'listadmin' : self.GetOwnerEmail(), - 'confirmurl' : confirmurl, - }, lang=lang, mlist=self) - msg = Message.UserNotification( - addr, self.GetRequestEmail(cookie), - text=text, lang=lang) - # BAW: See ChangeMemberAddress() for why we do it this way... - del msg['subject'] - msg['Subject'] = self.GetConfirmLeaveSubject(realname, cookie) - msg['Reply-To'] = self.GetRequestEmail(cookie) - msg.send(self) - - - # - # Miscellaneous stuff - # - def HasExplicitDest(self, msg): - """True if list name or any acceptable_alias is included among the - addresses in the recipient headers. - """ - # This is the list's full address. - listfullname = '%s@%s' % (self.internal_name(), self.host_name) - recips = [] - # Check all recipient addresses against the list's explicit addresses, - # specifically To: Cc: and Resent-to: - to = [] - for header in ('to', 'cc', 'resent-to', 'resent-cc'): - to.extend(getaddresses(msg.get_all(header, []))) - for fullname, addr in to: - # It's possible that if the header doesn't have a valid RFC 2822 - # value, we'll get None for the address. So skip it. - if addr is None: - continue - addr = addr.lower() - localpart = addr.split('@')[0] - if (# TBD: backwards compatibility: deprecated - localpart == self.internal_name() or - # exact match against the complete list address - addr == listfullname): - return True - recips.append((addr, localpart)) - # Helper function used to match a pattern against an address. - def domatch(pattern, addr): - try: - if re.match(pattern, addr, re.IGNORECASE): - return True - except re.error: - # The pattern is a malformed regexp -- try matching safely, - # with all non-alphanumerics backslashed: - if re.match(re.escape(pattern), addr, re.IGNORECASE): - return True - return False - # Here's the current algorithm for matching acceptable_aliases: - # - # 1. If the pattern does not have an `@' in it, we first try matching - # it against just the localpart. This was the behavior prior to - # 2.0beta3, and is kept for backwards compatibility. (deprecated). - # - # 2. If that match fails, or the pattern does have an `@' in it, we - # try matching against the entire recip address. - aliases = self.acceptable_aliases.splitlines() - for addr, localpart in recips: - for alias in aliases: - stripped = alias.strip() - if not stripped: - # Ignore blank or empty lines - continue - if '@' not in stripped and domatch(stripped, localpart): - return True - if domatch(stripped, addr): - return True - return False - - def parse_matching_header_opt(self): - """Return a list of triples [(field name, regex, line), ...].""" - # - Blank lines and lines with '#' as first char are skipped. - # - Leading whitespace in the matchexp is trimmed - you can defeat - # that by, eg, containing it in gratuitous square brackets. - all = [] - for line in self.bounce_matching_headers.split('\n'): - line = line.strip() - # Skip blank lines and lines *starting* with a '#'. - if not line or line[0] == "#": - continue - i = line.find(':') - if i < 0: - # This didn't look like a header line. BAW: should do a - # better job of informing the list admin. - syslog('config', 'bad bounce_matching_header line: %s\n%s', - self.real_name, line) - else: - header = line[:i] - value = line[i+1:].lstrip() - try: - cre = re.compile(value, re.IGNORECASE) - except re.error, e: - # The regexp was malformed. BAW: should do a better - # job of informing the list admin. - syslog('config', '''\ -bad regexp in bounce_matching_header line: %s -\n%s (cause: %s)''', self.real_name, value, e) - else: - all.append((header, cre, line)) - return all - - def hasMatchingHeader(self, msg): - """Return true if named header field matches a regexp in the - bounce_matching_header list variable. - - Returns constraint line which matches or empty string for no - matches. - """ - for header, cre, line in self.parse_matching_header_opt(): - for value in msg.get_all(header, []): - if cre.search(value): - return line - return 0 - - def autorespondToSender(self, sender, lang=None): - """Return true if Mailman should auto-respond to this sender. - - This is only consulted for messages sent to the -request address, or - for posting hold notifications, and serves only as a safety value for - mail loops with email 'bots. - """ - # language setting - if lang == None: - lang = self.preferred_language - i18n.set_language(lang) - # No limit - if mm_cfg.MAX_AUTORESPONSES_PER_DAY == 0: - return 1 - today = time.localtime()[:3] - info = self.hold_and_cmd_autoresponses.get(sender) - if info is None or info[0] <> today: - # First time we've seen a -request/post-hold for this sender - self.hold_and_cmd_autoresponses[sender] = (today, 1) - # BAW: no check for MAX_AUTORESPONSES_PER_DAY <= 1 - return 1 - date, count = info - if count < 0: - # They've already hit the limit for today. - syslog('vette', '-request/hold autoresponse discarded for: %s', - sender) - return 0 - if count >= mm_cfg.MAX_AUTORESPONSES_PER_DAY: - syslog('vette', '-request/hold autoresponse limit hit for: %s', - sender) - self.hold_and_cmd_autoresponses[sender] = (today, -1) - # Send this notification message instead - text = Utils.maketext( - 'nomoretoday.txt', - {'sender' : sender, - 'listname': '%s@%s' % (self.real_name, self.host_name), - 'num' : count, - 'owneremail': self.GetOwnerEmail(), - }, - lang=lang) - msg = Message.UserNotification( - sender, self.GetOwnerEmail(), - _('Last autoresponse notification for today'), - text, lang=lang) - msg.send(self) - return 0 - self.hold_and_cmd_autoresponses[sender] = (today, count+1) - return 1 - - def GetBannedPattern(self, email): - """Returns matched entry in ban_list if email matches. - Otherwise returns None. - """ - ban = False - for pattern in self.ban_list: - if pattern.startswith('^'): - # This is a regular expression match - try: - if re.search(pattern, email, re.IGNORECASE): - ban = True - break - except re.error: - # BAW: we should probably remove this pattern - pass - else: - # Do the comparison case insensitively - if pattern.lower() == email.lower(): - ban = True - break - if ban: - return pattern - else: - return None - - - - # - # Multilingual (i18n) support - # - def GetAvailableLanguages(self): - langs = self.available_languages - # If we don't add this, and the site admin has never added any - # language support to the list, then the general admin page may have a - # blank field where the list owner is supposed to chose the list's - # preferred language. - if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs: - langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # When testing, it's possible we've disabled a language, so just - # filter things out so we don't get tracebacks. - return [lang for lang in langs if mm_cfg.LC_DESCRIPTIONS.has_key(lang)] diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Utils.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Utils.py deleted file mode 100644 index 819315e..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/Utils.py +++ /dev/null @@ -1,874 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - - -"""Miscellaneous essential routines. - -This includes actual message transmission routines, address checking and -message and address munging, a handy-dandy routine to map a function on all -the mailing lists, and whatever else doesn't belong elsewhere. - -""" - -from __future__ import nested_scopes - -import os -import re -import cgi -import sha -import time -import errno -import base64 -import random -import urlparse -import htmlentitydefs -import email.Header -import email.Iterators -from email.Errors import HeaderParseError -from types import UnicodeType -from string import whitespace, digits -try: - # Python 2.2 - from string import ascii_letters -except ImportError: - # Older Pythons - _lower = 'abcdefghijklmnopqrstuvwxyz' - ascii_letters = _lower + _lower.upper() - -from Mailman import mm_cfg -from Mailman import Errors -from Mailman import Site -from Mailman.SafeDict import SafeDict -from Mailman.Logging.Syslog import syslog - -try: - True, False -except NameError: - True = 1 - False = 0 - -EMPTYSTRING = '' -UEMPTYSTRING = u'' -NL = '\n' -DOT = '.' -IDENTCHARS = ascii_letters + digits + '_' - -# Search for $(identifier)s strings, except that the trailing s is optional, -# since that's a common mistake -cre = re.compile(r'%\(([_a-z]\w*?)\)s?', re.IGNORECASE) -# Search for $$, $identifier, or ${identifier} -dre = re.compile(r'(\${2})|\$([_a-z]\w*)|\${([_a-z]\w*)}', re.IGNORECASE) - - - -def list_exists(listname): - """Return true iff list `listname' exists.""" - # The existance of any of the following file proves the list exists - # : config.pck, config.pck.last, config.db, config.db.last - # - # The former two are for 2.1alpha3 and beyond, while the latter two are - # for all earlier versions. - basepath = Site.get_listpath(listname) - for ext in ('.pck', '.pck.last', '.db', '.db.last'): - dbfile = os.path.join(basepath, 'config' + ext) - if os.path.exists(dbfile): - return True - return False - - -def list_names(): - """Return the names of all lists in default list directory.""" - # We don't currently support separate listings of virtual domains - return Site.get_listnames() - - - -# a much more naive implementation than say, Emacs's fill-paragraph! -def wrap(text, column=70, honor_leading_ws=True): - """Wrap and fill the text to the specified column. - - Wrapping is always in effect, although if it is not possible to wrap a - line (because some word is longer than `column' characters) the line is - broken at the next available whitespace boundary. Paragraphs are also - always filled, unless honor_leading_ws is true and the line begins with - whitespace. This is the algorithm that the Python FAQ wizard uses, and - seems like a good compromise. - - """ - wrapped = '' - # first split the text into paragraphs, defined as a blank line - paras = re.split('\n\n', text) - for para in paras: - # fill - lines = [] - fillprev = False - for line in para.split(NL): - if not line: - lines.append(line) - continue - if honor_leading_ws and line[0] in whitespace: - fillthis = False - else: - fillthis = True - if fillprev and fillthis: - # if the previous line should be filled, then just append a - # single space, and the rest of the current line - lines[-1] = lines[-1].rstrip() + ' ' + line - else: - # no fill, i.e. retain newline - lines.append(line) - fillprev = fillthis - # wrap each line - for text in lines: - while text: - if len(text) <= column: - line = text - text = '' - else: - bol = column - # find the last whitespace character - while bol > 0 and text[bol] not in whitespace: - bol -= 1 - # now find the last non-whitespace character - eol = bol - while eol > 0 and text[eol] in whitespace: - eol -= 1 - # watch out for text that's longer than the column width - if eol == 0: - # break on whitespace after column - eol = column - while eol < len(text) and text[eol] not in whitespace: - eol += 1 - bol = eol - while bol < len(text) and text[bol] in whitespace: - bol += 1 - bol -= 1 - line = text[:eol+1] + '\n' - # find the next non-whitespace character - bol += 1 - while bol < len(text) and text[bol] in whitespace: - bol += 1 - text = text[bol:] - wrapped += line - wrapped += '\n' - # end while text - wrapped += '\n' - # end for text in lines - # the last two newlines are bogus - return wrapped[:-2] - - - -def QuotePeriods(text): - JOINER = '\n .\n' - SEP = '\n.\n' - return JOINER.join(text.split(SEP)) - - -# This takes an email address, and returns a tuple containing (user,host) -def ParseEmail(email): - user = None - domain = None - email = email.lower() - at_sign = email.find('@') - if at_sign < 1: - return email, None - user = email[:at_sign] - rest = email[at_sign+1:] - domain = rest.split('.') - return user, domain - - -def LCDomain(addr): - "returns the address with the domain part lowercased" - atind = addr.find('@') - if atind == -1: # no domain part - return addr - return addr[:atind] + '@' + addr[atind+1:].lower() - - -# TBD: what other characters should be disallowed? -_badchars = re.compile(r'[][()<>|;^,\000-\037\177-\377]') - -def ValidateEmail(s): - """Verify that an email address isn't grossly evil.""" - # Pretty minimal, cheesy check. We could do better... - if not s or s.count(' ') > 0: - raise Errors.MMBadEmailError - if _badchars.search(s) or s[0] == '-': - raise Errors.MMHostileAddress, s - user, domain_parts = ParseEmail(s) - # This means local, unqualified addresses, are no allowed - if not domain_parts: - raise Errors.MMBadEmailError, s - if len(domain_parts) < 2: - raise Errors.MMBadEmailError, s - - - -# Patterns which may be used to form malicious path to inject a new -# line in the mailman error log. (TK: advisory by Moritz Naumann) -CRNLpat = re.compile(r'[^\x21-\x7e]') - -def GetPathPieces(envar='PATH_INFO'): - path = os.environ.get(envar) - if path: - if CRNLpat.search(path): - path = CRNLpat.split(path)[0] - syslog('error', 'Warning: Possible malformed path attack.') - return [p for p in path.split('/') if p] - return None - - - -def ScriptURL(target, web_page_url=None, absolute=False): - """target - scriptname only, nothing extra - web_page_url - the list's configvar of the same name - absolute - a flag which if set, generates an absolute url - """ - if web_page_url is None: - web_page_url = mm_cfg.DEFAULT_URL_PATTERN % get_domain() - if web_page_url[-1] <> '/': - web_page_url = web_page_url + '/' - fullpath = os.environ.get('REQUEST_URI') - if fullpath is None: - fullpath = os.environ.get('SCRIPT_NAME', '') + \ - os.environ.get('PATH_INFO', '') - baseurl = urlparse.urlparse(web_page_url)[2] - if not absolute and fullpath.endswith(baseurl): - # Use relative addressing - fullpath = fullpath[len(baseurl):] - i = fullpath.find('?') - if i > 0: - count = fullpath.count('/', 0, i) - else: - count = fullpath.count('/') - path = ('../' * count) + target - else: - path = web_page_url + target - return path + mm_cfg.CGIEXT - - - -def GetPossibleMatchingAddrs(name): - """returns a sorted list of addresses that could possibly match - a given name. - - For Example, given scott@pobox.com, return ['scott@pobox.com'], - given scott@blackbox.pobox.com return ['scott@blackbox.pobox.com', - 'scott@pobox.com']""" - - name = name.lower() - user, domain = ParseEmail(name) - res = [name] - if domain: - domain = domain[1:] - while len(domain) >= 2: - res.append("%s@%s" % (user, DOT.join(domain))) - domain = domain[1:] - return res - - - -def List2Dict(L, foldcase=False): - """Return a dict keyed by the entries in the list passed to it.""" - d = {} - if foldcase: - for i in L: - d[i.lower()] = True - else: - for i in L: - d[i] = True - return d - - - -_vowels = ('a', 'e', 'i', 'o', 'u') -_consonants = ('b', 'c', 'd', 'f', 'g', 'h', 'k', 'm', 'n', - 'p', 'r', 's', 't', 'v', 'w', 'x', 'z') -_syllables = [] - -for v in _vowels: - for c in _consonants: - _syllables.append(c+v) - _syllables.append(v+c) -del c, v - -def UserFriendly_MakeRandomPassword(length): - syls = [] - while len(syls) * 2 < length: - syls.append(random.choice(_syllables)) - return EMPTYSTRING.join(syls)[:length] - - -def Secure_MakeRandomPassword(length): - bytesread = 0 - bytes = [] - fd = None - try: - while bytesread < length: - try: - # Python 2.4 has this on available systems. - newbytes = os.urandom(length - bytesread) - except (AttributeError, NotImplementedError): - if fd is None: - try: - fd = os.open('/dev/urandom', os.O_RDONLY) - except OSError, e: - if e.errno <> errno.ENOENT: - raise - # We have no available source of cryptographically - # secure random characters. Log an error and fallback - # to the user friendly passwords. - syslog('error', - 'urandom not available, passwords not secure') - return UserFriendly_MakeRandomPassword(length) - newbytes = os.read(fd, length - bytesread) - bytes.append(newbytes) - bytesread += len(newbytes) - s = base64.encodestring(EMPTYSTRING.join(bytes)) - # base64 will expand the string by 4/3rds - return s.replace('\n', '')[:length] - finally: - if fd is not None: - os.close(fd) - - -def MakeRandomPassword(length=mm_cfg.MEMBER_PASSWORD_LENGTH): - if mm_cfg.USER_FRIENDLY_PASSWORDS: - return UserFriendly_MakeRandomPassword(length) - return Secure_MakeRandomPassword(length) - - -def GetRandomSeed(): - chr1 = int(random.random() * 52) - chr2 = int(random.random() * 52) - def mkletter(c): - if 0 <= c < 26: - c += 65 - if 26 <= c < 52: - #c = c - 26 + 97 - c += 71 - return c - return "%c%c" % tuple(map(mkletter, (chr1, chr2))) - - - -def set_global_password(pw, siteadmin=True): - if siteadmin: - filename = mm_cfg.SITE_PW_FILE - else: - filename = mm_cfg.LISTCREATOR_PW_FILE - # rw-r----- - omask = os.umask(026) - try: - fp = open(filename, 'w') - fp.write(sha.new(pw).hexdigest() + '\n') - fp.close() - finally: - os.umask(omask) - - -def get_global_password(siteadmin=True): - if siteadmin: - filename = mm_cfg.SITE_PW_FILE - else: - filename = mm_cfg.LISTCREATOR_PW_FILE - try: - fp = open(filename) - 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 None - return challenge - - -def check_global_password(response, siteadmin=True): - challenge = get_global_password(siteadmin) - if challenge is None: - return None - return challenge == sha.new(response).hexdigest() - - - -def websafe(s): - return cgi.escape(s, quote=True) - - -def nntpsplit(s): - parts = s.split(':', 1) - if len(parts) == 2: - try: - return parts[0], int(parts[1]) - except ValueError: - pass - # Use the defaults - return s, 119 - - - -# Just changing these two functions should be enough to control the way -# that email address obscuring is handled. -def ObscureEmail(addr, for_text=False): - """Make email address unrecognizable to web spiders, but invertable. - - When for_text option is set (not default), make a sentence fragment - instead of a token.""" - if for_text: - return addr.replace('@', ' at ') - else: - return addr.replace('@', '--at--') - -def UnobscureEmail(addr): - """Invert ObscureEmail() conversion.""" - # Contrived to act as an identity operation on already-unobscured - # emails, so routines expecting obscured ones will accept both. - return addr.replace('--at--', '@') - - - -class OuterExit(Exception): - pass - -def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): - # Make some text from a template file. The order of searches depends on - # whether mlist and lang are provided. Once the templatefile is found, - # string substitution is performed by interpolation in `dict'. If `raw' - # is false, the resulting text is wrapped/filled by calling wrap(). - # - # When looking for a template in a specific language, there are 4 places - # that are searched, in this order: - # - # 1. the list-specific language directory - # lists// - # - # 2. the domain-specific language directory - # templates// - # - # 3. the site-wide language directory - # templates/site/ - # - # 4. the global default language directory - # templates/ - # - # The first match found stops the search. In this way, you can specialize - # templates at the desired level, or, if you use only the default - # templates, you don't need to change anything. You should never modify - # files in the templates/ subdirectory, since Mailman will - # overwrite these when you upgrade. That's what the templates/site - # language directories are for. - # - # A further complication is that the language to search for is determined - # by both the `lang' and `mlist' arguments. The search order there is - # that if lang is given, then the 4 locations above are searched, - # substituting lang for . If no match is found, and mlist is - # given, then the 4 locations are searched using the list's preferred - # language. After that, the server default language is used for - # . If that still doesn't yield a template, then the standard - # distribution's English language template is used as an ultimate - # fallback. If that's missing you've got big problems. ;) - # - # A word on backwards compatibility: Mailman versions prior to 2.1 stored - # templates in templates/*.{html,txt} and lists//*.{html,txt}. - # Those directories are no longer searched so if you've got customizations - # in those files, you should move them to the appropriate directory based - # on the above description. Mailman's upgrade script cannot do this for - # you. - # - # The function has been revised and renamed as it now returns both the - # template text and the path from which it retrieved the template. The - # original function is now a wrapper which just returns the template text - # as before, by calling this renamed function and discarding the second - # item returned. - # - # Calculate the languages to scan - languages = [] - if lang is not None: - languages.append(lang) - if mlist is not None: - languages.append(mlist.preferred_language) - languages.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # Calculate the locations to scan - searchdirs = [] - if mlist is not None: - searchdirs.append(mlist.fullpath()) - searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, mlist.host_name)) - searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, 'site')) - searchdirs.append(mm_cfg.TEMPLATE_DIR) - # Start scanning - fp = None - try: - for lang in languages: - for dir in searchdirs: - filename = os.path.join(dir, lang, templatefile) - try: - fp = open(filename) - raise OuterExit - except IOError, e: - if e.errno <> errno.ENOENT: raise - # Okay, it doesn't exist, keep looping - fp = None - except OuterExit: - pass - if fp is None: - # Try one last time with the distro English template, which, unless - # you've got a really broken installation, must be there. - try: - filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) - fp = open(filename) - except IOError, e: - if e.errno <> errno.ENOENT: raise - # We never found the template. BAD! - raise IOError(errno.ENOENT, 'No template file found', templatefile) - template = fp.read() - fp.close() - text = template - if dict is not None: - try: - sdict = SafeDict(dict) - try: - text = sdict.interpolate(template) - except UnicodeError: - # Try again after coercing the template to unicode - utemplate = unicode(template, GetCharSet(lang), 'replace') - text = sdict.interpolate(utemplate) - except (TypeError, ValueError), e: - # The template is really screwed up - syslog('error', 'broken template: %s\n%s', filename, e) - pass - if raw: - return text, filename - return wrap(text), filename - - -def maketext(templatefile, dict=None, raw=False, lang=None, mlist=None): - return findtext(templatefile, dict, raw, lang, mlist)[0] - - - -ADMINDATA = { - # admin keyword: (minimum #args, maximum #args) - 'confirm': (1, 1), - 'help': (0, 0), - 'info': (0, 0), - 'lists': (0, 0), - 'options': (0, 0), - 'password': (2, 2), - 'remove': (0, 0), - 'set': (3, 3), - 'subscribe': (0, 3), - 'unsubscribe': (0, 1), - 'who': (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 is_administrivia(msg): - linecnt = 0 - lines = [] - for line in email.Iterators.body_line_iterator(msg): - # Strip out any signatures - if line == '-- ': - break - if line.strip(): - linecnt += 1 - if linecnt > mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES: - return False - 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 True - # Look at the first N lines and see if there is any administrivia on the - # line. BAW: N is currently hardcoded to 5. str-ify the Subject: header - # because it may be an email.Header.Header instance rather than a string. - bodylines = lines[:5] - subject = str(msg.get('subject', '')) - bodylines.append(subject) - for line in bodylines: - if not line.strip(): - continue - 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 True - return False - - - -def GetRequestURI(fallback=None, escape=True): - """Return the full virtual path this CGI script was invoked with. - - Newer web servers seems to supply this info in the REQUEST_URI - environment variable -- which isn't part of the CGI/1.1 spec. - Thus, if REQUEST_URI isn't available, we concatenate SCRIPT_NAME - and PATH_INFO, both of which are part of CGI/1.1. - - Optional argument `fallback' (default `None') is returned if both of - the above methods fail. - - The url will be cgi escaped to prevent cross-site scripting attacks, - unless `escape' is set to 0. - """ - url = fallback - if os.environ.has_key('REQUEST_URI'): - url = os.environ['REQUEST_URI'] - elif os.environ.has_key('SCRIPT_NAME') and os.environ.has_key('PATH_INFO'): - url = os.environ['SCRIPT_NAME'] + os.environ['PATH_INFO'] - if escape: - return websafe(url) - return url - - -# Wait on a dictionary of child pids -def reap(kids, func=None, once=False): - while kids: - if func: - func() - try: - pid, status = os.waitpid(-1, os.WNOHANG) - except OSError, e: - # If the child procs had a bug we might have no children - if e.errno <> errno.ECHILD: - raise - kids.clear() - break - if pid <> 0: - try: - del kids[pid] - except KeyError: - # Huh? How can this happen? - pass - if once: - break - - -def GetLanguageDescr(lang): - return mm_cfg.LC_DESCRIPTIONS[lang][0] - - -def GetCharSet(lang): - return mm_cfg.LC_DESCRIPTIONS[lang][1] - -def IsLanguage(lang): - return mm_cfg.LC_DESCRIPTIONS.has_key(lang) - - - -def get_domain(): - host = os.environ.get('HTTP_HOST', os.environ.get('SERVER_NAME')) - port = os.environ.get('SERVER_PORT') - # Strip off the port if there is one - if port and host.endswith(':' + port): - host = host[:-len(port)-1] - if mm_cfg.VIRTUAL_HOST_OVERVIEW and host: - return host.lower() - else: - # See the note in Defaults.py concerning DEFAULT_URL - # vs. DEFAULT_URL_HOST. - hostname = ((mm_cfg.DEFAULT_URL - and urlparse.urlparse(mm_cfg.DEFAULT_URL)[1]) - or mm_cfg.DEFAULT_URL_HOST) - return hostname.lower() - - -def get_site_email(hostname=None, extra=None): - if hostname is None: - hostname = mm_cfg.VIRTUAL_HOSTS.get(get_domain(), get_domain()) - if extra is None: - return '%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, hostname) - return '%s-%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, extra, hostname) - - - -# This algorithm crafts a guaranteed unique message-id. The theory here is -# that pid+listname+host will distinguish the message-id for every process on -# the system, except when process ids wrap around. To further distinguish -# message-ids, we prepend the integral time in seconds since the epoch. It's -# still possible that we'll vend out more than one such message-id per second, -# so we prepend a monotonically incrementing serial number. It's highly -# unlikely that within a single second, there'll be a pid wraparound. -_serial = 0 -def unique_message_id(mlist): - global _serial - msgid = '' % ( - _serial, time.time(), os.getpid(), - mlist.internal_name(), mlist.host_name) - _serial += 1 - return msgid - - -# Figure out epoch seconds of midnight at the start of today (or the given -# 3-tuple date of (year, month, day). -def midnight(date=None): - if date is None: - date = time.localtime()[:3] - # -1 for dst flag tells the library to figure it out - return time.mktime(date + (0,)*5 + (-1,)) - - - -# Utilities to convert from simplified $identifier substitutions to/from -# standard Python $(identifier)s substititions. The "Guido rules" for the -# former are: -# $$ -> $ -# $identifier -> $(identifier)s -# ${identifier} -> $(identifier)s - -def to_dollar(s): - """Convert from %-strings to $-strings.""" - s = s.replace('$', '$$').replace('%%', '%') - parts = cre.split(s) - for i in range(1, len(parts), 2): - if parts[i+1] and parts[i+1][0] in IDENTCHARS: - parts[i] = '${' + parts[i] + '}' - else: - parts[i] = '$' + parts[i] - return EMPTYSTRING.join(parts) - - -def to_percent(s): - """Convert from $-strings to %-strings.""" - s = s.replace('%', '%%').replace('$$', '$') - parts = dre.split(s) - for i in range(1, len(parts), 4): - if parts[i] is not None: - parts[i] = '$' - elif parts[i+1] is not None: - parts[i+1] = '%(' + parts[i+1] + ')s' - else: - parts[i+2] = '%(' + parts[i+2] + ')s' - return EMPTYSTRING.join(filter(None, parts)) - - -def dollar_identifiers(s): - """Return the set (dictionary) of identifiers found in a $-string.""" - d = {} - for name in filter(None, [b or c or None for a, b, c in dre.findall(s)]): - d[name] = True - return d - - -def percent_identifiers(s): - """Return the set (dictionary) of identifiers found in a %-string.""" - d = {} - for name in cre.findall(s): - d[name] = True - return d - - - -# Utilities to canonicalize a string, which means un-HTML-ifying the string to -# produce a Unicode string or an 8-bit string if all the characters are ASCII. -def canonstr(s, lang=None): - newparts = [] - parts = re.split(r'&(?P[^;]+);', s) - def appchr(i): - if i < 256: - newparts.append(chr(i)) - else: - newparts.append(unichr(i)) - while True: - newparts.append(parts.pop(0)) - if not parts: - break - ref = parts.pop(0) - if ref.startswith('#'): - try: - appchr(int(ref[1:])) - except ValueError: - # Non-convertable, stick with what we got - newparts.append('&'+ref+';') - else: - c = htmlentitydefs.entitydefs.get(ref, '?') - if c.startswith('#') and c.endswith(';'): - appchr(int(ref[1:-1])) - else: - newparts.append(c) - newstr = EMPTYSTRING.join(newparts) - if isinstance(newstr, UnicodeType): - return newstr - # We want the default fallback to be iso-8859-1 even if the language is - # English (us-ascii). This seems like a practical compromise so that - # non-ASCII characters in names can be used in English lists w/o having to - # change the global charset for English from us-ascii (which I - # superstitiously think may have unintended consequences). - if lang is None: - charset = 'iso-8859-1' - else: - charset = GetCharSet(lang) - if charset == 'us-ascii': - charset = 'iso-8859-1' - return unicode(newstr, charset, 'replace') - - -# The opposite of canonstr() -- sorta. I.e. it attempts to encode s in the -# charset of the given language, which is the character set that the page will -# be rendered in, and failing that, replaces non-ASCII characters with their -# html references. It always returns a byte string. -def uncanonstr(s, lang=None): - if s is None: - s = u'' - if lang is None: - charset = 'us-ascii' - else: - charset = GetCharSet(lang) - # See if the string contains characters only in the desired character - # set. If so, return it unchanged, except for coercing it to a byte - # string. - try: - if isinstance(s, UnicodeType): - return s.encode(charset) - else: - u = unicode(s, charset) - return s - except UnicodeError: - # Nope, it contains funny characters, so html-ref it - return uquote(s) - - -def uquote(s): - a = [] - for c in s: - o = ord(c) - if o > 127: - a.append('&#%3d;' % o) - else: - a.append(c) - # Join characters together and coerce to byte string - return str(EMPTYSTRING.join(a)) - - -def oneline(s, cset): - # Decode header string in one line and convert into specified charset - try: - h = email.Header.make_header(email.Header.decode_header(s)) - ustr = h.__unicode__() - line = UEMPTYSTRING.join(ustr.splitlines()) - return line.encode(cset, 'replace') - except (LookupError, UnicodeError, ValueError, HeaderParseError): - # possibly charset problem. return with undecoded string in one line. - return EMPTYSTRING.join(s.splitlines()) diff --git a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/htmlformat.py b/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/htmlformat.py deleted file mode 100644 index 10fb601..0000000 --- a/Identity/Webenv/App/Mailman/2.1.9-4.el5/Mailman/htmlformat.py +++ /dev/null @@ -1,779 +0,0 @@ -# Copyright (C) 1998-2006 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 -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - - -"""Library for program-based construction of an HTML documents. - -Encapsulate HTML formatting directives in classes that act as containers -for python and, recursively, for nested HTML formatting objects. -""" - - -# Eventually could abstract down to HtmlItem, which outputs an arbitrary html -# object given start / end tags, valid options, and a value. Ug, objects -# shouldn't be adding their own newlines. The next object should. - - -import types -import cgi -import os - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.i18n import _ - -SPACE = ' ' -EMPTYSTRING = '' -NL = '\n' - -# Format an arbitrary object. -def HTMLFormatObject(item, indent): - "Return a peesentation of an object, invoking their Format method if any." - if type(item) == type(''): - return item - elif not hasattr(item, "Format"): - return `item` - else: - return item.Format(indent) - -def CaseInsensitiveKeyedDict(d): - result = {} - for (k,v) in d.items(): - result[k.lower()] = v - return result - -# Given references to two dictionaries, copy the second dictionary into the -# first one. -def DictMerge(destination, fresh_dict): - for (key, value) in fresh_dict.items(): - destination[key] = value - -class Table: - def __init__(self, **table_opts): - self.cells = [] - self.cell_info = {} - self.row_info = {} - self.opts = table_opts - - def AddOptions(self, opts): - DictMerge(self.opts, opts) - - # Sets all of the cells. It writes over whatever cells you had there - # previously. - - def SetAllCells(self, cells): - self.cells = cells - - # Add a new blank row at the end - def NewRow(self): - self.cells.append([]) - - # Add a new blank cell at the end - def NewCell(self): - self.cells[-1].append('') - - def AddRow(self, row): - self.cells.append(row) - - def AddCell(self, cell): - self.cells[-1].append(cell) - - def AddCellInfo(self, row, col, **kws): - kws = CaseInsensitiveKeyedDict(kws) - if not self.cell_info.has_key(row): - self.cell_info[row] = { col : kws } - elif self.cell_info[row].has_key(col): - DictMerge(self.cell_info[row], kws) - else: - self.cell_info[row][col] = kws - - def AddRowInfo(self, row, **kws): - kws = CaseInsensitiveKeyedDict(kws) - if not self.row_info.has_key(row): - self.row_info[row] = kws - else: - DictMerge(self.row_info[row], kws) - - # What's the index for the row we just put in? - def GetCurrentRowIndex(self): - return len(self.cells)-1 - - # What's the index for the col we just put in? - def GetCurrentCellIndex(self): - return len(self.cells[-1])-1 - - def ExtractCellInfo(self, info): - valid_mods = ['rowspan', 'colspan', 'css'] - output = '' - for (key, val) in info.items(): - if not key in valid_mods: - continue - if key == 'css': - output = output + ' %s' % val - else: - output = output + ' %s="%s"' % (key, val) - return output - - def ExtractRowInfo(self, info): - valid_mods = ['css'] - output = '' - for (key, val) in info.items(): - if not key in valid_mods: - continue - output = output + ' %s' % val - return output - - def ExtractTableInfo(self, info): - valid_mods = ['css'] - output = '' - for (key, val) in info.items(): - if not key in valid_mods: - continue - output = output + ' %s' % val - return output - - def FormatCell(self, row, col, indent): - try: - my_info = self.cell_info[row][col] - except: - my_info = None - - output = '\n' + ' '*indent + '' - - for i in range(len(self.cells[row])): - output = output + self.FormatCell(row, i, indent + 2) - - output = output + '\n' + ' '*indent + '' - - return output - - def Format(self, indent=0): - output = '\n' + ' '*indent + '' - - for i in range(len(self.cells)): - output = output + self.FormatRow(i, indent + 2) - - output = output + '\n' + ' '*indent + '
      \n' - - return output - - -class Link: - def __init__(self, href, text, target=None): - self.href = href - self.text = text - self.target = target - - def Format(self, indent=0): - texpr = "" - if self.target != None: - texpr = ' target="%s"' % self.target - return '%s' % (HTMLFormatObject(self.href, indent), - texpr, - HTMLFormatObject(self.text, indent)) - -class FontSize: - """FontSize is being deprecated - use FontAttr(..., size="...") instead.""" - def __init__(self, size, *items): - self.items = list(items) - self.size = size - - def Format(self, indent=0): - output = '' % self.size - for item in self.items: - output = output + HTMLFormatObject(item, indent) - output = output + '' - return output - -class FontAttr: - """Present arbitrary font attributes.""" - def __init__(self, *items, **kw): - self.items = list(items) - self.attrs = kw - - def Format(self, indent=0): - seq = [] - for k, v in self.attrs.items(): - seq.append('%s="%s"' % (k, v)) - output = '' % SPACE.join(seq) - for item in self.items: - output = output + HTMLFormatObject(item, indent) - output = output + '' - return output - - -class Container: - def __init__(self, *items): - if not items: - self.items = [] - else: - self.items = items - - def AddItem(self, obj): - self.items.append(obj) - - def addMessage(self, errmsg, tag=None, css=None): - if css is None: - css = 'class="message"' - self.AddItem(_('\n
      ')) - if tag is None: - self.AddItem(Paragraph(errmsg)) - else: - self.AddItem(Header(3, tag).Format() + Paragraph(errmsg).Format()) - self.AddItem('
      ') - - def Format(self, indent=0): - output = [] - for item in self.items: - output.append(HTMLFormatObject(item, indent)) - return EMPTYSTRING.join(output) - - -class Label(Container): - align = 'right' - - def __init__(self, *items): - Container.__init__(self, *items) - - def Format(self, indent=0): - return ('
      ' % self.align) + \ - Container.Format(self, indent) + \ - '
      ' - -# My own standard document template. YMMV. -# something more abstract would be more work to use... - -class Document(Container): - title = None - language = None - bgcolor = mm_cfg.WEB_BG_COLOR - suppress_head = 0 - - def set_language(self, lang=None): - self.language = lang - - def set_bgcolor(self, color): - self.bgcolor = color - - def SetTitle(self, title): - self.title = title - - def Format(self, indent=0, **kws): - charset = 'us-ascii' - if self.language: - charset = Utils.GetCharSet(self.language) - output = ['Content-Type: text/html; charset=%s\n' % charset] - if not self.suppress_head: - kws.setdefault('bgcolor', self.bgcolor) - tab = ' ' * indent - output.extend(['', - '', - '', - '', - ]) - if mm_cfg.IMAGE_LOGOS: - output.append('' % - (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) - # Hit all the bases - output.append('' % charset) - if self.title: - output.append('%s%s' % (tab, self.title)) - output.append('') - output.append('') - output.append('%s' % tab) - quals = [] - # Default link colors - if mm_cfg.WEB_VLINK_COLOR: - kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR) - if mm_cfg.WEB_ALINK_COLOR: - kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR) - if mm_cfg.WEB_LINK_COLOR: - kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) - for k, v in kws.items(): - quals.append('%s="%s"' % (k, v)) - - listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() - listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() - siteowner = Utils.get_site_email() - adminmail_link = Link('mailto:' + siteowner, siteowner).Format() - output.extend([''' - - - - -
      - Lists -

      ''' + _('%(listinfo_link)s | %(listadmin_link)s') + '''

      -

      ''' + _('Mailing Lists') + '''

      -
      - - -

      - -

      - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/articles/home.php b/Identity/Webenv/App/Puntal/themes/punbb/articles/home.php deleted file mode 100755 index 4d10121..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/articles/home.php +++ /dev/null @@ -1,58 +0,0 @@ - - -
      -

      -
      -
      - - - - - - - - - - - - - fetch()) : ?> - - - - - - - - - - -
      - -
      -
      -
      \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/articles/index.php b/Identity/Webenv/App/Puntal/themes/punbb/articles/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/articles/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/articles/main.php b/Identity/Webenv/App/Puntal/themes/punbb/articles/main.php deleted file mode 100755 index f40388a..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/articles/main.php +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - - - - - - -
      -
      - - - -
      -
      - -
      -

      -
      -
      - %s
    ', '
  • %s
  • ', '
  • %s
  • ') ?> - - - -
    -

    - %s', '
  • %s
  • '); ?> -
    - - - - - - - %s

    '); ?> - - - - - - - - - - - - - - - - - -
    - - -
    -
    - - - - - - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/articles/rss.php b/Identity/Webenv/App/Puntal/themes/punbb/articles/rss.php deleted file mode 100755 index 8398f13..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/articles/rss.php +++ /dev/null @@ -1,42 +0,0 @@ - - - - <?php tpl::headTitlePage() ?> - <?php tpl::lang('Articles') ?> - - ]]> - - Puntal 2 - - fetch()) : ?> - - <?php articles::articleRssTitle() ?> - - - - - - - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/articles/search_results.php b/Identity/Webenv/App/Puntal/themes/punbb/articles/search_results.php deleted file mode 100755 index d547eb2..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/articles/search_results.php +++ /dev/null @@ -1,44 +0,0 @@ - - - -isEmpty()) : ?> - -

    - - fetch()) : ?> - -

    -
    - '.tpl::lang('Read more',true).''); ?> -
    - - -
    - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/blog/blog.css b/Identity/Webenv/App/Puntal/themes/punbb/blog/blog.css deleted file mode 100755 index 8925f26..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/blog/blog.css +++ /dev/null @@ -1,28 +0,0 @@ - -/* Titres -----------------------------------------------------------*/ -#puntal_content #blogTitle span { - font-weight: bold; -} -.cal { - font-size : 75%; - font-variant: small-caps; - font-weight: bold; -} -.cal table { - border-spacing: 0; - border-collapse: separate; - caption-side: top; -} -.day-date { - text-align: right; - font-style: italic; - font-weight: bold; -} -.post-title { - margin-bottom: 0; -} -.post-info { - margin-top: 0; - font-size: 0.9em; -} \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/blog/form.php b/Identity/Webenv/App/Puntal/themes/punbb/blog/form.php deleted file mode 100755 index 7e4538c..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/blog/form.php +++ /dev/null @@ -1,83 +0,0 @@ - - -

    '.tpl::lang('Error', true).'

    %s'); ?> -%s

    '); ?> - -
    -
    -
    -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    -generatePtb('c_content'); -?>
    - -

    -
    -
    - -

    -
    -
    -
    -

    -

    - -

    - - -

    -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/blog/list.php b/Identity/Webenv/App/Puntal/themes/punbb/blog/list.php deleted file mode 100755 index 033cec6..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/blog/list.php +++ /dev/null @@ -1,63 +0,0 @@ - - - - -fetch()) : ?> - %s

    '); ?> - -
    -

    -
    -
    - - -
    > - Lire la suite

    '); ?> -
    - - -
    -
    -
    - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/blog/main.php b/Identity/Webenv/App/Puntal/themes/punbb/blog/main.php deleted file mode 100755 index b4dec10..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/blog/main.php +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - <?php blog::dcSinglePostTitle('%s - '); blog::dcSingleCatTitle('%s - '); - blog::dcSingleMonthTitle('%s - '); blog::dcCustomTitle('%s - '); tpl::infos(); ?> - - - - - - - - - -
    -
    - - - -
    -
    - - -
    -

    -
    -
    -

    - -
    -
    -
    - - -

    -
    -
    - -
    -
    - - - - - - - - - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/blog/post.php b/Identity/Webenv/App/Puntal/themes/punbb/blog/post.php deleted file mode 100755 index 8b876bd..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/blog/post.php +++ /dev/null @@ -1,118 +0,0 @@ - - -
    -

    -
    -
    - - - %s
    '); ?> -
    -
    -
    - - -
    -
    -
    -

    - isEmpty()) : /* Message si aucune trackback */?> -

    - - - fetch()) : /* Liste des trackbacks */ - // On met le num�ro du trackback dans une variable - $tb_num = $trackbacks->int_index+1; - ?> -

    - . - , -

    - - -
    - -
    - - - - -

    -

    - -

    - -
    -
    -
    - -
    -
    -
    -

    - isEmpty()) : /* Message si aucune commentaire */ ?> -

    - - - fetch()) : /* Boucle de commentaires */ - // On met le num�ro du commentaire dans une variable - $co_num = $comments->int_index+1; - ?> -

    - . - , -

    - - -
    - -
    - - -

    - - - - - -

    - -
    -
    -
    - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/blog/search_results.php b/Identity/Webenv/App/Puntal/themes/punbb/blog/search_results.php deleted file mode 100755 index bcd116c..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/blog/search_results.php +++ /dev/null @@ -1,60 +0,0 @@ -isEmpty()) : ?> -

    - - -fetch()) : ?> - -

    - - - -
    > - Lire la suite

    '); ?> -
    - - - - -
    - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/bugtracker.css b/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/bugtracker.css deleted file mode 100755 index cbbf56a..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/bugtracker.css +++ /dev/null @@ -1,74 +0,0 @@ - -/* Titres -----------------------------------------------------------*/ -#puntal_content #bugTrackerTitle span { - font-weight: bold; -} - -#puntal_content #bugsFilter { - border: none; - font-weight: normal; - width: auto; -} - -/* Menu -----------------------------------------------------------*/ -#bugtrackerMenu { - list-style-type: none; - padding-left: 0; -} -#bugtrackerMenu li { - display: inline; - margin-right: 1em; -} - -/* S�v�rit�s -----------------------------------------------------------*/ -table.bugs td.sev_1 { background-color: #fff5dd; } -table.bugs td.sev_2 { background-color: #ecdbb7; } -table.bugs td.sev_3 { background-color: #f5d5c6; } -table.bugs td.sev_4 { background-color: #F7B390; } -table.bugs td.sev_5 { background-color: #f3a29b; } - -#puntal_content .bugTitle { - width: auto; - border: none; - margin: 0; - font-weight: normal; -} - - -/* Barre d'avancement -----------------------------------------------------------*/ -div.av_bar_ext { - border: 1px solid #004925; - background-color: #fff; - width: 100px; - height: 10px; - text-align: left; -} -div.av_bar_int { - background-color: #060; - height: 10px; -} -p.percent_txt { - margin: 0; - padding: 0; - display: inline; -} - - -/* Commentaires -----------------------------------------------------------*/ -.addLinkTop { - text-align: right; - margin-bottom: 0; -} -.addLinkBottom { - text-align: right; - margin-top: 0; -} - -.comment { - margin: 0 0 1em 0; -} diff --git a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/form_add.php b/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/form_add.php deleted file mode 100755 index 4cd75f4..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/form_add.php +++ /dev/null @@ -1,109 +0,0 @@ - - - - -
    -

    -
    -
    - %s', '
  • %s
  • ') ?> -
    -
    -
    - - - - -
    -

    -
    -
    - -
    -
    -
    - - -
    -

    -
    -
    -
    -
    -
    - - -

    -

    - -

    -

    - - -

    -

    - -
    -

    - - -

    - -

    -

    -
    - -

    -

    - -

    -

    -generatePtb('neo_desc'); -?> -
    -
    -
    -

    - - - -

    -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/index.php b/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/list.php b/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/list.php deleted file mode 100755 index 81b6ebf..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/list.php +++ /dev/null @@ -1,71 +0,0 @@ - - -
    -

    -
    -
    -
    -
    - -
    -

    -

    - -

    -

    -
    -
    -
    -

    -

    -
    -
    -
    - -
    -
    - -
    -
    - -
    -

    -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/main.php b/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/main.php deleted file mode 100755 index 7feb243..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/main.php +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - - - - - - - - -
    -
    - - - -
    -
    - -
    -

    -
    -
    -
      - %s', - '
    • %s
    • ' ) ?> - - %s', - '
    • %s
    • ' ) ?> -
    -
    -
    -
    - - - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/search_results.php b/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/search_results.php deleted file mode 100755 index 74c6504..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/search_results.php +++ /dev/null @@ -1,42 +0,0 @@ - - - -isEmpty()) : ?> - -

    - - fetch()) : ?> - -

    -
    - - - -
    - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/viewbug.php b/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/viewbug.php deleted file mode 100755 index 8e22f7f..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/bugtracker/viewbug.php +++ /dev/null @@ -1,64 +0,0 @@ - - -
    -

    -
    -
    - -

    -

    / %

    -
    -
    -
    -
    -
    - - -

    - -
    -

    -
    -
    - - fetch()) : ?> -

    -

    -
    - - -

    - -
    -
    -
    - -

    - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/calendar/add_event.php b/Identity/Webenv/App/Puntal/themes/punbb/calendar/add_event.php deleted file mode 100755 index 58947d8..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/calendar/add_event.php +++ /dev/null @@ -1,162 +0,0 @@ - - -
    -

    -
    -
    -

    - %s', '
  • %s
  • '); ?> -
    -
    -
    - - - - -
    -

    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - - - -
    -

    -
    -
    - -
    -
    - -

    - -

    -
    -
    - -
    -
    - -
    -

    - - - - - -

    -
    -
    -
    -
    -
    - -
    - -

    -

    - -

    -

    - -
    - - -

    - -

    - -

    -

    - -

    -

    -generatePtb('req_message'); -?> - -
    -
    -
    - 0) : ?> - -
    -
    - -
    -
    - - - - - - - - -
    -
    -
    -
    - - -

    - - - -

    -
    -
    -
    \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/calendar/calendar.css b/Identity/Webenv/App/Puntal/themes/punbb/calendar/calendar.css deleted file mode 100755 index f34fbb6..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/calendar/calendar.css +++ /dev/null @@ -1,61 +0,0 @@ -#puntal_content #calendarTitle span { - font-weight: bold; -} - -/* tableaux */ -table.calendar { -} -#puntal_content table.minicalendar { - float: left; - width: 49%; -} -#puntal_content table.minicalendar caption { -} - -/* bloc de texte */ -div#infos_cal { - float: right; - width: 60%; - margin-top: 1em; -} -div#infos_cal h3 { - margin-top: 1em; -} - -/* cellules */ -table.calendar td { - height: 75px; - width: 14%; - vertical-align: top; -} -table.calendar td.inactive { - background: #ddd; -} -table.calendar td.past { - color: #ccc; -} -table.calendar td.today { - background: #fff; -} -table.calendar td.active { - border: 1px solid #c00; -} - -/* liens */ -table.calendar td.past a { - color: #ccc; -} -table.calendar td.today a {} -table.calendar td.active a {} - -/* numero jour */ -table.calendar td p.daynumber {} - -/* liste �v�nement dans cellules */ -table.calendar td ul.eventlist { - font-size: 0.9em; -} -table.calendar td ul.eventlist li { - list-style: square inside -} - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/calendar/edit_event.php b/Identity/Webenv/App/Puntal/themes/punbb/calendar/edit_event.php deleted file mode 100755 index d0259b8..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/calendar/edit_event.php +++ /dev/null @@ -1,157 +0,0 @@ - - -
    -

    -
    -
    -

    -
      - %s
    ', '
  • %s
  • '); - ?> - -
    -
    -
    - - - - -
    -

    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - - -
    -

    -
    -
    - -
    -
    - -

    - -

    -
    -
    - -
    -
    - -
    - - - - - - -
    -
    -
    -
    -
    - -
    - - - - -
    - - - -generatePtb('req_message'); -?> - -
    -
    -
    - - 0) : ?> - -
    -
    - -
    -
    - - - - - - - - - - - - -
    -
    -
    -
    - -

    - - - - - -

    -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/calendar/list.php b/Identity/Webenv/App/Puntal/themes/punbb/calendar/list.php deleted file mode 100755 index 7782f7b..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/calendar/list.php +++ /dev/null @@ -1,47 +0,0 @@ -isEmpty()) : ?> - - - fetch()) : ?> -
    -

    -
    -
    - -
    -
    - - - %s
    ');?> - - - - - - - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/calendar/main.php b/Identity/Webenv/App/Puntal/themes/punbb/calendar/main.php deleted file mode 100755 index 08788e0..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/calendar/main.php +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - - - -
    -
    - - - - -
    -
    - - - - - - - - -
    -

    - -
    -
    - -
    -
    -
    -
    - - - - - - - - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/calendar/search_results.php b/Identity/Webenv/App/Puntal/themes/punbb/calendar/search_results.php deleted file mode 100755 index 7c7c239..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/calendar/search_results.php +++ /dev/null @@ -1,44 +0,0 @@ - - - -isEmpty()) : ?> - -

    - - fetch()) : ?> - -

    -
    - -
    - - - -
    - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/date-picker.css b/Identity/Webenv/App/Puntal/themes/punbb/date-picker.css deleted file mode 100755 index d4677bc..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/date-picker.css +++ /dev/null @@ -1,35 +0,0 @@ -.date-picker { - border-collapse: collapse; - background: #fff; - color: #fff; - border: 1px solid #666; - border-width: 1px 2px 2px 1px; -} -.date-picker th { - border: none; - color: #000; - text-align: center; -} -.date-picker td { - border: 1px solid #666; - text-align: center; - padding: 4px 6px; -} -th.date-picker-month { - text-align: left; -} -th.date-picker-year { - text-align: right; -} -.date-picker-control, th.date-picker-control { - color: #060; - cursor: pointer; -} -.date-picker-day, .date-picker-today { - color: #000; - background: #eee; - cursor: pointer; -} -.date-picker-today { - background: #ccc; -} \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/cat.php b/Identity/Webenv/App/Puntal/themes/punbb/directory/cat.php deleted file mode 100755 index 3efdcc8..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/cat.php +++ /dev/null @@ -1,102 +0,0 @@ - - -
    - - %s

    ') ?> - -

    -
    -
    - %s', '
  • %s
  • ', '
  • %s
  • ') ?> - - - isEmpty()) : ?> - -

    - -
      - fetch()) : ?> - -
    • - %s
    ', '
  • %s (%s)
  • ') ?> - - - -
    - - - - -
    -
    - -
    -

    '.tpl::lang('Websites',true).'

    -
    -
    ', - /* le bloc si il y a pas de lien */ '
    -
    -
    -
    -

    '.tpl::lang('Websites',true).'

    -
    -
    ', - /* un item : */ '

    %s

    %s', - /* nombre de liens � afficher : */ '2' , - /* option de tri */ 'random' - ) ?> - - isEmpty()) : ?> - - fetch()) : ?> - - - - -

    - -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/dir.css b/Identity/Webenv/App/Puntal/themes/punbb/directory/dir.css deleted file mode 100755 index 15d815c..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/dir.css +++ /dev/null @@ -1,96 +0,0 @@ - -/* Lien cat�gorie parent -----------------------------------------------------------*/ -.parentCatLink { - text-align: right; - float: right; -} - -/* Titres -----------------------------------------------------------*/ -#puntal_content .directory span { - font-weight: bold; -} - -/* Menu -----------------------------------------------------------*/ -#dirMenu { - list-style-none: none; - padding-left: 0; -} -#dirMenu li { - display: inline; - margin-right: 1em; -} -#dirMenu li.active { - font-weight: bold; -} - -/* Totaux -----------------------------------------------------------*/ -.dirIntro { - text-align: center; -} - -/* Liste des cat�gories de l'annuaire -----------------------------------------------------------*/ -ul.cats { - margin: 0; - padding: 0; - list-style: none; -} -ul.cats li { - margin: 1.5em 0 1.5em 0; - padding: 0 1em 0 0; - float: left; - width: 48%; - height: auto; - line-height: 160%; - font-weight: bold; -} - -/* Liste des sous-cat�gories de l'annuaire -----------------------------------------------------------*/ -ul.cats ul.subcats { - margin: 0; - padding: 0; - list-style: none; - font-size: 0.9em; -} -ul.cats ul.subcats li { - background: none; - float: none; - display: block; - margin: 0; - padding: 0 0 0 20px; - line-height: 160%; - font-weight: normal; -} -ul.cats ul.subcats li a { - background: none; - padding-left: 0; - text-decoration: none; -} -ul.cats ul.subcats li a:hover { - text-decoration: underline; -} - -/* Les liens -----------------------------------------------------------*/ -.links { - padding-bottom: 2em; -} - -/* Google Page Ranks -----------------------------------------------------------*/ -.pageranks { padding-left: 45px; } -.pagerank1 { background: transparent url(img/pr1.png) no-repeat 0 50%; } -.pagerank2 { background: transparent url(img/pr2.png) no-repeat 0 50%; } -.pagerank3 { background: transparent url(img/pr3.png) no-repeat 0 50%; } -.pagerank4 { background: transparent url(img/pr4.png) no-repeat 0 50%; } -.pagerank5 { background: transparent url(img/pr5.png) no-repeat 0 50%; } -.pagerank6 { background: transparent url(img/pr6.png) no-repeat 0 50%; } -.pagerank7 { background: transparent url(img/pr7.png) no-repeat 0 50%; } -.pagerank8 { background: transparent url(img/pr8.png) no-repeat 0 50%; } -.pagerank9 { background: transparent url(img/pr9.png) no-repeat 0 50%; } -.pagerank10 { background: transparent url(img/pr10.png) no-repeat 0 50%; } diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/home.php b/Identity/Webenv/App/Puntal/themes/punbb/directory/home.php deleted file mode 100755 index 421926a..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/home.php +++ /dev/null @@ -1,76 +0,0 @@ - - -
    -

    -
    -
    - %s', '
  • %s
  • ', '
  • %s
  • ') ?> -

    -

    - - -
    -
    - -
    -

    '.tpl::lang('Categories',true).'

    -
    -
    ', - /* le bloc si il y a pas de lien */ '
    -
    -
    -
    -

    '.tpl::lang('Categories',true).'

    -
    -
    ', - /* un item : */ '

    %s

    %s', - /* nombre de liens � afficher : */ '2' , - /* option de tri */ 'random' - ) ?> - - isEmpty()) : ?> -
      - fetch()) : ?> - -
    • - %s
    ', '
  • %s (%s)
  • ') ?> - - - -
    - -
    -
    -
    - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr10.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr10.png deleted file mode 100755 index 9399a1e..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr10.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr2.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr2.png deleted file mode 100755 index 42dcb6e..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr2.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr3.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr3.png deleted file mode 100755 index 78403ba..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr3.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr4.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr4.png deleted file mode 100755 index fc49050..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr4.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr5.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr5.png deleted file mode 100755 index b914b91..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr5.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr6.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr6.png deleted file mode 100755 index 174b1da..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr6.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr7.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr7.png deleted file mode 100755 index e159117..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr7.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr8.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr8.png deleted file mode 100755 index 39ffeee..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr8.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr9.png b/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr9.png deleted file mode 100755 index 0d18f45..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/directory/img/pr9.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/index.php b/Identity/Webenv/App/Puntal/themes/punbb/directory/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/list.php b/Identity/Webenv/App/Puntal/themes/punbb/directory/list.php deleted file mode 100755 index 279ee9b..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/list.php +++ /dev/null @@ -1,62 +0,0 @@ - - -
    -

    -
    -
    - %s', '
  • %s
  • ', '
  • %s
  • ') ?> -

    -
    -
    -
    - -
    -

    -
    -
    - isEmpty()) : ?> - - fetch()) : ?> - - - - -

    - -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/main.php b/Identity/Webenv/App/Puntal/themes/punbb/directory/main.php deleted file mode 100755 index 61a1f54..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/main.php +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - - -
    -
    - - - -
    -
    - - - - - - - - - - - - - - - - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/search.php b/Identity/Webenv/App/Puntal/themes/punbb/directory/search.php deleted file mode 100755 index 59a88b5..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/search.php +++ /dev/null @@ -1,49 +0,0 @@ - - -

    Recherche sur l'annuaire

    - R�sultats de votre recherche de %s.

    '); ?> - - isEmpty()) : ?> - - fetch()) : ?> - -

    >

    - -
      -
    • -
    • Adresse :
    • -
    • Rubrique :
    • - Google PageRank : %s'); ?> -
    - - - -

    Aucun r�sultats.

    - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/search_results.php b/Identity/Webenv/App/Puntal/themes/punbb/directory/search_results.php deleted file mode 100755 index da69418..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/search_results.php +++ /dev/null @@ -1,45 +0,0 @@ - - - -isEmpty()) : ?> - -

    - - fetch()) : ?> - - -
    - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/directory/submit.php b/Identity/Webenv/App/Puntal/themes/punbb/directory/submit.php deleted file mode 100755 index 6240ba6..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/directory/submit.php +++ /dev/null @@ -1,85 +0,0 @@ - - -
    -

    -
    -
    - %s', '
  • %s
  • ', '
  • %s
  • ') ?> -

    -
    -
    -
    - - - -
    -

    -
    -
    - -
    -
    -
    - - -
    - -
    -
    -
    -
    -
    - -

    -

    - -

    -

    - -

    -

    - -

    -

    -generatePtb('l_desc'); -?> -

    -

    - -
    -
    -
    -

    - -

    -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/downloads/cat.php b/Identity/Webenv/App/Puntal/themes/punbb/downloads/cat.php deleted file mode 100755 index 27e3727..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/downloads/cat.php +++ /dev/null @@ -1,27 +0,0 @@ - - - - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/downloads/dl_rss.php b/Identity/Webenv/App/Puntal/themes/punbb/downloads/dl_rss.php deleted file mode 100755 index e349327..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/downloads/dl_rss.php +++ /dev/null @@ -1,36 +0,0 @@ - - - - <?php tpl::headTitlePage() ?> - - - - Puntal 2 - - - - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/downloads/downloads.css b/Identity/Webenv/App/Puntal/themes/punbb/downloads/downloads.css deleted file mode 100755 index 15534d0..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/downloads/downloads.css +++ /dev/null @@ -1,114 +0,0 @@ -/* Titres -----------------------------------------------------------*/ -#puntal_content #downloadsTitle span { - font-weight: bold; -} - -/* Cat�gories */ - -#catBox ul { -} - -#catBox li { - display: inline; - margin-right: 12px; -} -#catBox li.current { - font-weight: bold; -} - -/* Contenu des downloads -----------------------------------------------------------*/ -#downloadContent { -} - -#downloadContent p { - margin: 1em 0; -} - -#downloadContent pre { - background-color: #333; - color: #fff; - padding: 5px; - border: 1px solid #ccc; -} - -#downloadContent h3, #downloadContent h4, #downloadContent h5, #downloadContent h6 { - display: block; - font-weight: bold; -} - -#downloadContent h3 { - font-size: 1.4em; - margin: 1em 0; -} -#downloadContent h4 { - font-size: 1.3em; - margin: 1.33em 0; -} -#downloadContent h5 { - font-size: 1.2em; - margin: 1.67em 0; -} -#downloadContent h6 { - font-size: 1.1em; - margin: 2.33em 0; -} - - -/* Lists settings from Mozilla Firefox */ -#downloadContent ul, -#downloadContent ol { - display: block; - margin: 1em 0; - padding-left: 40px; -} - -#downloadContent ul, -#downloadContent ul li { - list-style-type: disc; -} - -#downloadContent ol, -#downloadContent ol li { - list-style-type: decimal; -} - -#downloadContent li { - display: list-item; -} - - -/* nested lists have no top/bottom margins */ -#downloadContent ul ul, -#downloadContent ul ol, -#downloadContent ul dl, -#downloadContent ol ul, -#downloadContent ol ol, -#downloadContent ol dl, -#downloadContent dl ul, -#downloadContent dl ol, -#downloadContent dl dl { - margin-top: 0; - margin-bottom: 0; -} - -/* 2 deep unordered lists use a circle */ -#downloadContent ol ul, -#downloadContent ul ul, -#downloadContent ol ul li, -#downloadContent ul ul li { - list-style-type: circle; -} - -/* 3 deep (or more) unordered lists use a square */ -#downloadContent ol ol ul, -#downloadContent ol ul ul, -#downloadContent ul ol ul, -#downloadContent ul ul ul, -#downloadContent ol ol ul li, -#downloadContent ol ul ul li, -#downloadContent ul ol ul li, -#downloadContent ul ul ul li { - list-style-type: square; -} diff --git a/Identity/Webenv/App/Puntal/themes/punbb/downloads/file.php b/Identity/Webenv/App/Puntal/themes/punbb/downloads/file.php deleted file mode 100755 index 88b57b2..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/downloads/file.php +++ /dev/null @@ -1,39 +0,0 @@ - - - -
    -
    -
    - -
    -
    -
    -
    - - -
    -

    -
    -
    - - - %s

    ', # le lien "normal" - '

    %s

    ' # si pas autoris� - ) ?> - - -

    - - - - %s') ?> -

    -
    -
    -
    -
    - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/downloads/home.php b/Identity/Webenv/App/Puntal/themes/punbb/downloads/home.php deleted file mode 100755 index dfae838..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/downloads/home.php +++ /dev/null @@ -1,80 +0,0 @@ - - - - -
    -

    -
    - - - - - - - - - - fetch()) : ?> - - - - - - - -

    -
    -
    - - - - - -
    -

    -
    - - - - - - - - - fetch()) : ?> - - - - - - -

    -
    -
    - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/downloads/index.php b/Identity/Webenv/App/Puntal/themes/punbb/downloads/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/downloads/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/downloads/main.php b/Identity/Webenv/App/Puntal/themes/punbb/downloads/main.php deleted file mode 100755 index 59c6bba..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/downloads/main.php +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - - - - -
    -
    - - - -
    -
    - -
    -

    -
    -
    - - %s', # format du bloc - '
  • %s
  • ', # format d'un �l�ment - '
  • %s
  • ', # format d'un �l�ment actif - ' (%s)' # format du nombre de t�l�chargement dans une cat�gorie - ) ?> - - - - %s', '
  • %s
  • ') ?> - -
    -
    -
    - - - - - - - - - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/downloads/search_results.php b/Identity/Webenv/App/Puntal/themes/punbb/downloads/search_results.php deleted file mode 100755 index d7de7c7..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/downloads/search_results.php +++ /dev/null @@ -1,42 +0,0 @@ - - - -isEmpty()) : ?> - -

    - - fetch()) : ?> - -

    -
    - - - -
    - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/error.php b/Identity/Webenv/App/Puntal/themes/punbb/error.php deleted file mode 100755 index a1c2602..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/error.php +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - -
    -
    - - - -
    -
    - - - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/img/index.php b/Identity/Webenv/App/Puntal/themes/punbb/img/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/img/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/img/logo-forums.png b/Identity/Webenv/App/Puntal/themes/punbb/img/logo-forums.png deleted file mode 100644 index 789553c..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/img/logo-forums.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/img/minus.png b/Identity/Webenv/App/Puntal/themes/punbb/img/minus.png deleted file mode 100755 index b58fcc3..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/img/minus.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/img/plus.png b/Identity/Webenv/App/Puntal/themes/punbb/img/plus.png deleted file mode 100755 index 8eebc1c..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/img/plus.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/index.php b/Identity/Webenv/App/Puntal/themes/punbb/index.php deleted file mode 100755 index 48d200f..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/form_add.php b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/form_add.php deleted file mode 100755 index 9572ccb..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/form_add.php +++ /dev/null @@ -1,73 +0,0 @@ - - - - -
    -

    -
    -
    - %s', '
  • %s
  • ') ?> -
    -
    -
    - - -
    -

    -
    -
    -
    -
    -
    -

    -

    -

    -

    -generatePtb('p_def'); -?> -

    -

    -generatePtb('p_ex'); -?> -
    -
    -
    -

    - - - -

    -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/form_edit.php b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/form_edit.php deleted file mode 100755 index 1f3deb5..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/form_edit.php +++ /dev/null @@ -1,74 +0,0 @@ - - - - -
    -

    -
    -
    - %s', '
  • %s
  • ') ?> -
    -
    -
    - - -
    -

    -
    -
    -
    -
    -
    - -

    -

    - -

    -

    -generatePtb('p_def'); -?> -

    -

    -generatePtb('p_ex'); -?> -
    -
    -
    -

    - - -

    -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/index.php b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/lexicon.css b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/lexicon.css deleted file mode 100755 index 0462e65..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/lexicon.css +++ /dev/null @@ -1,39 +0,0 @@ -#puntal_content .lexiconTitle span { - font-weight: bold; -} -#puntal_content .word { - border: none; - margin: 0; - font-weight: normal; -} - -p#letter_list { text-align: center; } - -p#letter_list a.cur_letter { font-weight: bold; } - -div.lexique dt.word { - margin-top: 1em; - font-weight: bold; -} - -div.lexique dd.word_def {} - -div.lexique dd.word_example { - font-style: italic; -} -div.lexique dd.word_example em { - font-style: normal; -} - -div.lexique dd.word_infos { - text-align: right; -} - -div.lexique dt.inactive, -div.lexique dd.inactive { - color: #696969; -} - -#puntal_content #lexiqueTitle span { - font-weight: bold; -} diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_all.php b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_all.php deleted file mode 100755 index c5e8530..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_all.php +++ /dev/null @@ -1,45 +0,0 @@ - - -
    -
    - -
    -
    - -
    -

    -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_letter.php b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_letter.php deleted file mode 100755 index e8e7e02..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_letter.php +++ /dev/null @@ -1,59 +0,0 @@ - - -
    -

    -

    -
    -
    - isEmpty()) : ?> -
    - fetch()) : ?> -
    -
    -
    -
    - %s') ?> - - -
    - - - - %s') ?> - - %s') ?> - %s') ?> - -
    - - - -
    - -

    - -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_validate.php b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_validate.php deleted file mode 100755 index 4516958..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/list_validate.php +++ /dev/null @@ -1,52 +0,0 @@ - - -
    -

    -
    -
    - isEmpty()) : ?> -
    - fetch()) : ?> -
    -
    - %s') ?> -
    - - - - %s') ?> - %s') ?> - %s') ?> -
    - -
    - -

    - -
    -
    -
    - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/main.php b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/main.php deleted file mode 100755 index b2cc0e7..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/main.php +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - - -
    -
    - - - -
    -
    - -
    -

    -
    -

    - %s') ?> - - %s') ?> - - - %s', - ' - %s' ) ?> - - %s', - ' - %s' ) ?> - - %s', - ' - %s' ) ?> -

    -

    - %s', - '%s', - ' - ') ?> -

    -
    -
    - - - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/search_results.php b/Identity/Webenv/App/Puntal/themes/punbb/lexicon/search_results.php deleted file mode 100755 index 290721c..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/lexicon/search_results.php +++ /dev/null @@ -1,42 +0,0 @@ - - - -isEmpty()) : ?> - -

    - - fetch()) : ?> - -

    -
    - - - -
    - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/news/index.php b/Identity/Webenv/App/Puntal/themes/punbb/news/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/news/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/news/main.php b/Identity/Webenv/App/Puntal/themes/punbb/news/main.php deleted file mode 100755 index 5503511..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/news/main.php +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - - - -
    -
    - - - -
    -
    - - - - %s

    ') ?> - - - fetch()) : ?> -
    - -

    -
    -
    - - -

    - -
    - - |

    -
    -
    -
    - - - -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - -*/ -?> - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/news/news.css b/Identity/Webenv/App/Puntal/themes/punbb/news/news.css deleted file mode 100755 index a99078a..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/news/news.css +++ /dev/null @@ -1,46 +0,0 @@ - -/* News title -----------------------------------------------------------*/ - -h2.news a:link { text-decoration: none; color: #EEE } -h2.news a:hover { text-decoration: underline; color: #FFF; } -h2.news a:active { text-decoration: underline; color: #FFF; } -h2.news a:visited { text-decoration: none; color: #EEE; } -h2.news a:visited:hover { text-decoration: underline; color: #FFF; } - -/* Infos news -----------------------------------------------------------*/ -p.infos { - text-align: right; -} - -/* Avatar -----------------------------------------------------------*/ -.newsAvatar { - float: left; - text-align: left; - clear: left; - width: auto; - padding: 1em; - margin: 0; -} - -.newsAvatar img { - padding-right: 1em; - padding-bottom: 1em; -} - -/* RSS buton -----------------------------------------------------------*/ -a.rss:link, a.rss:visited, a.rss:active { - color: #fff; - background: #f90; - border: 1px outset #f90; - text-decoration: none; - padding: 0.1em 0.3em; - font-size: 85%; -} -a.rss:hover { - color: #fff; - border: 1px inset #f90; -} diff --git a/Identity/Webenv/App/Puntal/themes/punbb/news/news_static.tpl b/Identity/Webenv/App/Puntal/themes/punbb/news/news_static.tpl deleted file mode 100755 index e62d051..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/news/news_static.tpl +++ /dev/null @@ -1,12 +0,0 @@ - -
    -

    -
    -
    - - - -

    |

    -
    -
    -
    diff --git a/Identity/Webenv/App/Puntal/themes/punbb/news/rss.php b/Identity/Webenv/App/Puntal/themes/punbb/news/rss.php deleted file mode 100755 index 884b70d..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/news/rss.php +++ /dev/null @@ -1,43 +0,0 @@ - - - - <?php tpl::headTitlePage() ?> - <?php tpl::lang('News') ?> - - ]]> - - Puntal 2 - - fetch()) : ?> - - <?php news::title() ?> - - ]]> - - - - - - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/planet/index.php b/Identity/Webenv/App/Puntal/themes/punbb/planet/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/planet/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/planet/main.php b/Identity/Webenv/App/Puntal/themes/punbb/planet/main.php deleted file mode 100755 index 9bee0b4..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/planet/main.php +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - - -
    -
    - - - -
    -
    - fetch()) : ?> -
    -

    -

    - -
    - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/planet/planet.css b/Identity/Webenv/App/Puntal/themes/punbb/planet/planet.css deleted file mode 100755 index 2ed536f..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/planet/planet.css +++ /dev/null @@ -1,27 +0,0 @@ -/* Titres */ -#puntal_content .feedTitle { - margin-bottom: 0; -} - -/* Infos */ -#puntal_content .feedInfos { - margin-top: 0; - font-size: 0.9em; -} - -/* Liens */ -#puntal_content .feedLinks { - font-size: 0.9em; - list-style-type: none; - padding-left: 0; - text-align: right; -} -#puntal_content .feedLinks li { - display: inline; - margin-right: 1em; -} -#puntal_content .feedLinks li a { - padding: 0.2em 0 0.2em 18px; -} - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/redirect.php b/Identity/Webenv/App/Puntal/themes/punbb/redirect.php deleted file mode 100755 index 8585c82..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/redirect.php +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - -
    -
    - -
    -

    -
    -
    -

    -

    -
    -
    -
    - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/search/main.php b/Identity/Webenv/App/Puntal/themes/punbb/search/main.php deleted file mode 100755 index 620d840..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/search/main.php +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - <?php tpl::headTitlePage() ?> - - - - - - - - -
    -
    - - - -
    -
    - -
    -

    -
    -
    - - -
    -
    -
    - -
    -

    -
    -
    -
    -

    -

    -
    -
    -
    -
    - -
    -
    - -
    - - -
    -
    - - - -
    -
    - - - diff --git a/Identity/Webenv/App/Puntal/themes/punbb/style.css b/Identity/Webenv/App/Puntal/themes/punbb/style.css deleted file mode 100755 index 4d28aac..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/style.css +++ /dev/null @@ -1,70 +0,0 @@ -/* En-tete / header */ -div#brdtitle { position: relative; } - -p#prelude { - position: absolute; - top: 0.3em; - right: 0.3em; - font-size: 0.8em; -} - -p#prelude a:link { text-decoration: none; color: #EEE } -p#prelude a:hover { text-decoration: underline; color: #FFF; } -p#prelude a:active { text-decoration: underline; color: #FFF; } -p#prelude a:visited { text-decoration: none; color: #EEE; } -p#prelude a:visited:hover { text-decoration: underline; color: #FFF; } - -/* Colone � gauche / Column on left */ -div#puntal_main { - width: 100%; - float: right; - margin-left: -22em; -} -div#puntal_content { - margin-left: 22em; - padding-left: 1ex; -} -div#puntal_sidebar { - width: 21em; - float: left; -} - -/* Styles for collapse icon and text */ -div.block h2 img.icon { - vertical-align: text-bottom; -} -div.block h2 a { - text-decoration: none; -} - -/* Listes colone lat�rale / Lateral column list */ -div#puntal_sidebar li { - padding-top: 0.2em; - padding-bottom: 0.2em; -} -div#puntal_sidebar li.active { - font-weight: bold; -} - - -/* Sous-menu t�l�chargements / Downloads sub-menu */ -#sousmenu ul, #sousmenu li { - list-style-type: none; - display: inline; -} -#sousmenu li { margin-right: 12px; } -#sousmenu a:link, #sousmenu a:visited{ text-decoration: none; } -#sousmenu a:hover { text-decoration: underline; } - -/* Boite bloc �dito / Edito block box */ -#box_edito div.box { border-width: 1px; } -#box_edito div.box p { - line-height: 150%; - letter-spacing: 0.08em; -} - -/* Boite bloc RSS reader / RSS reader block box */ -#box_rssreader h3 { - margin-top: 1em; - font-weight: bold; -} diff --git a/Identity/Webenv/App/Puntal/themes/punbb/tribune/img/index.php b/Identity/Webenv/App/Puntal/themes/punbb/tribune/img/index.php deleted file mode 100755 index 7931413..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/tribune/img/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/tribune/img/supprimer.png b/Identity/Webenv/App/Puntal/themes/punbb/tribune/img/supprimer.png deleted file mode 100755 index b6ed32d..0000000 Binary files a/Identity/Webenv/App/Puntal/themes/punbb/tribune/img/supprimer.png and /dev/null differ diff --git a/Identity/Webenv/App/Puntal/themes/punbb/tribune/index.php b/Identity/Webenv/App/Puntal/themes/punbb/tribune/index.php deleted file mode 100755 index 12dd1e9..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/tribune/index.php +++ /dev/null @@ -1,27 +0,0 @@ - \ No newline at end of file diff --git a/Identity/Webenv/App/Puntal/themes/punbb/tribune/tribune.css b/Identity/Webenv/App/Puntal/themes/punbb/tribune/tribune.css deleted file mode 100755 index 0d9174e..0000000 --- a/Identity/Webenv/App/Puntal/themes/punbb/tribune/tribune.css +++ /dev/null @@ -1,4 +0,0 @@ -.wall { -height:100px; -overflow:auto; -} \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/Archiver.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/Archiver.py new file mode 100644 index 0000000..ba611c6 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/Archiver.py @@ -0,0 +1,251 @@ +# Copyright (C) 1998-2003 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +"""Mixin class for putting new messages in the right place for archival. + +Public archives are separated from private ones. An external archival +mechanism (eg, pipermail) should be pointed to the right places, to do the +archival. +""" + +import os +import errno +import traceback +import re +from cStringIO import StringIO + +from Mailman import mm_cfg +from Mailman import Mailbox +from Mailman import Utils +from Mailman import Site +from Mailman.SafeDict import SafeDict +from Mailman.Logging.Syslog import syslog +from Mailman.i18n import _ +from Mailman.htmlformat import Link +from Mailman.htmlformat import HTMLFormatObject + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +def makelink(old, new): + try: + os.symlink(old, new) + except OSError, e: + if e.errno <> errno.EEXIST: + raise + +def breaklink(link): + try: + os.unlink(link) + except OSError, e: + if e.errno <> errno.ENOENT: + raise + + + +class Archiver: + # + # Interface to Pipermail. HyperArch.py uses this method to get the + # archive directory for the mailing list + # + def InitVars(self): + # Configurable + self.archive = mm_cfg.DEFAULT_ARCHIVE + # 0=public, 1=private: + self.archive_private = mm_cfg.DEFAULT_ARCHIVE_PRIVATE + self.archive_volume_frequency = \ + mm_cfg.DEFAULT_ARCHIVE_VOLUME_FREQUENCY + # The archive file structure by default is: + # + # archives/ + # private/ + # listname.mbox/ + # listname.mbox + # listname/ + # lots-of-pipermail-stuff + # public/ + # listname.mbox@ -> ../private/listname.mbox + # listname@ -> ../private/listname + # + # IOW, the mbox and pipermail archives are always stored in the + # private archive for the list. This is safe because archives/private + # is always set to o-rx. Public archives have a symlink to get around + # the private directory, pointing directly to the private/listname + # which has o+rx permissions. Private archives do not have the + # symbolic links. + omask = os.umask(0) + try: + try: + os.mkdir(self.archive_dir()+'.mbox', 02775) + except OSError, e: + if e.errno <> errno.EEXIST: raise + # We also create an empty pipermail archive directory into + # which we'll drop an empty index.html file into. This is so + # that lists that have not yet received a posting have + # /something/ as their index.html, and don't just get a 404. + try: + os.mkdir(self.archive_dir(), 02775) + except OSError, e: + if e.errno <> errno.EEXIST: raise + # See if there's an index.html file there already and if not, + # write in the empty archive notice. + indexfile = os.path.join(self.archive_dir(), 'index.html') + fp = None + try: + fp = open(indexfile) + except IOError, e: + if e.errno <> errno.ENOENT: raise + omask = os.umask(002) + try: + fp = open(indexfile, 'w') + finally: + os.umask(omask) + fp.write(Utils.maketext( + 'emptyarchive.html', + {'listname': self.real_name, + 'listname_lower': self.real_name.lower(), + 'listinfo': self.GetScriptURL('listinfo', absolute=1), + # Links on header section (errormsg) + 'listadmin_link': Link(Utils.ScriptURL('admin'), _('Administration')).Format(), + 'listinfo_link': Link(Utils.ScriptURL('listinfo'), _('General Information')).Format(), + 'errormsg_header': _('Mailing Lists'), + }, mlist=self)) + if fp: + fp.close() + finally: + os.umask(omask) + + def archive_dir(self): + return Site.get_archpath(self.internal_name()) + + def ArchiveFileName(self): + """The mbox name where messages are left for archive construction.""" + return os.path.join(self.archive_dir() + '.mbox', + self.internal_name() + '.mbox') + + def GetBaseArchiveURL(self): + url = self.GetScriptURL('private', absolute=1) + '/' + if self.archive_private: + return url + else: + hostname = re.match('[^:]*://([^/]*)/.*', url).group(1)\ + or mm_cfg.DEFAULT_URL_HOST + url = mm_cfg.PUBLIC_ARCHIVE_URL % { + 'listname': self.internal_name(), + 'hostname': hostname + } + if not url.endswith('/'): + url += '/' + return url + + def __archive_file(self, afn): + """Open (creating, if necessary) the named archive file.""" + omask = os.umask(002) + try: + return Mailbox.Mailbox(open(afn, 'a+')) + finally: + os.umask(omask) + + # + # old ArchiveMail function, retained under a new name + # for optional archiving to an mbox + # + def __archive_to_mbox(self, post): + """Retain a text copy of the message in an mbox file.""" + try: + afn = self.ArchiveFileName() + mbox = self.__archive_file(afn) + mbox.AppendMessage(post) + mbox.fp.close() + except IOError, msg: + syslog('error', 'Archive file access failure:\n\t%s %s', afn, msg) + raise + + def ExternalArchive(self, ar, txt): + d = SafeDict({'listname': self.internal_name(), + 'hostname': self.host_name, + }) + cmd = ar % d + extarch = os.popen(cmd, 'w') + extarch.write(txt) + status = extarch.close() + if status: + syslog('error', 'external archiver non-zero exit status: %d\n', + (status & 0xff00) >> 8) + + # + # archiving in real time this is called from list.post(msg) + # + def ArchiveMail(self, msg): + """Store postings in mbox and/or pipermail archive, depending.""" + # Fork so archival errors won't disrupt normal list delivery + if mm_cfg.ARCHIVE_TO_MBOX == -1: + return + # + # We don't need an extra archiver lock here because we know the list + # itself must be locked. + if mm_cfg.ARCHIVE_TO_MBOX in (1, 2): + self.__archive_to_mbox(msg) + if mm_cfg.ARCHIVE_TO_MBOX == 1: + # Archive to mbox only. + return + txt = str(msg) + # should we use the internal or external archiver? + private_p = self.archive_private + if mm_cfg.PUBLIC_EXTERNAL_ARCHIVER and not private_p: + self.ExternalArchive(mm_cfg.PUBLIC_EXTERNAL_ARCHIVER, txt) + elif mm_cfg.PRIVATE_EXTERNAL_ARCHIVER and private_p: + self.ExternalArchive(mm_cfg.PRIVATE_EXTERNAL_ARCHIVER, txt) + else: + # use the internal archiver + f = StringIO(txt) + import HyperArch + h = HyperArch.HyperArchive(self) + h.processUnixMailbox(f) + h.close() + f.close() + + # + # called from MailList.MailList.Save() + # + def CheckHTMLArchiveDir(self): + # We need to make sure that the archive directory has the right perms + # for public vs private. If it doesn't exist, or some weird + # permissions errors prevent us from stating the directory, it's + # pointless to try to fix the perms, so we just return -scott + if mm_cfg.ARCHIVE_TO_MBOX == -1: + # Archiving is completely disabled, don't require the skeleton. + return + pubdir = Site.get_archpath(self.internal_name(), public=True) + privdir = self.archive_dir() + pubmbox = pubdir + '.mbox' + privmbox = privdir + '.mbox' + if self.archive_private: + breaklink(pubdir) + breaklink(pubmbox) + else: + # BAW: privdir or privmbox could be nonexistant. We'd get an + # OSError, ENOENT which should be caught and reported properly. + makelink(privdir, pubdir) + # Only make this link if the site has enabled public mbox files + if mm_cfg.PUBLIC_MBOX: + makelink(privmbox, pubmbox) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/HyperArch.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/HyperArch.py new file mode 100644 index 0000000..c319629 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/HyperArch.py @@ -0,0 +1,1344 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""HyperArch: Pipermail archiving for Mailman + + - The Dragon De Monsyne + + TODO: + - Should be able to force all HTML to be regenerated next time the + archive is run, in case a template is changed. + - Run a command to generate tarball of html archives for downloading + (probably in the 'update_dirty_archives' method). +""" + +from __future__ import nested_scopes + +import sys +import re +import errno +import urllib +import time +import os +import types +import HyperDatabase +import pipermail +import weakref +import binascii + +from email.Header import decode_header, make_header +from email.Errors import HeaderParseError +from email.Charset import Charset + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Errors +from Mailman import LockFile +from Mailman import MailList +from Mailman import i18n +from Mailman.SafeDict import SafeDict +from Mailman.Logging.Syslog import syslog +from Mailman.Mailbox import ArchiverMailbox +from Mailman.htmlformat import Link +from Mailman.htmlformat import HTMLFormatObject + +# Set up i18n. Assume the current language has already been set in the caller. +_ = i18n._ + +gzip = None +if mm_cfg.GZIP_ARCHIVE_TXT_FILES: + try: + import gzip + except ImportError: + pass + +EMPTYSTRING = '' +NL = '\n' + +# MacOSX has a default stack size that is too small for deeply recursive +# regular expressions. We see this as crashes in the Python test suite when +# running test_re.py and test_sre.py. The fix is to set the stack limit to +# 2048; the general recommendation is to do in the shell before running the +# test suite. But that's inconvenient for a daemon like the qrunner. +# +# AFAIK, this problem only affects the archiver, so we're adding this work +# around to this file (it'll get imported by the bundled pipermail or by the +# bin/arch script. We also only do this on darwin, a.k.a. MacOSX. +if sys.platform == 'darwin': + try: + import resource + except ImportError: + pass + else: + soft, hard = resource.getrlimit(resource.RLIMIT_STACK) + newsoft = min(hard, max(soft, 1024*2048)) + resource.setrlimit(resource.RLIMIT_STACK, (newsoft, hard)) + + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +def html_quote(s, lang=None): + repls = ( ('&', '&'), + ("<", '<'), + (">", '>'), + ('"', '"')) + for thing, repl in repls: + s = s.replace(thing, repl) + return Utils.uncanonstr(s, lang) + + +def url_quote(s): + return urllib.quote(s) + +def null_to_space(s): + return s.replace('\000', ' ') + + +def sizeof(filename, lang): + try: + size = os.path.getsize(filename) + except OSError, e: + # ENOENT can happen if the .mbox file was moved away or deleted, and + # an explicit mbox file name was given to bin/arch. + if e.errno <> errno.ENOENT: raise + return _('size not available') + if size < 1000: + # Avoid i18n side-effects + otrans = i18n.get_translation() + try: + i18n.set_language(lang) + out = _(' %(size)i bytes ') + finally: + i18n.set_translation(otrans) + return out + elif size < 1000000: + return ' %d KB ' % (size / 1000) + # GB?? :-) + return ' %d MB ' % (size / 1000000) + + +html_charset = '' + +def CGIescape(arg, lang=None): + if isinstance(arg, types.UnicodeType): + s = Utils.websafe(arg) + else: + s = Utils.websafe(str(arg)) + return Utils.uncanonstr(s.replace('"', '"'), lang) + +# Parenthesized human name +paren_name_pat = re.compile(r'([(].*[)])') + +# Subject lines preceded with 'Re:' +REpat = re.compile( r"\s*RE\s*(\[\d+\]\s*)?:\s*", re.IGNORECASE) + +# E-mail addresses and URLs in text +emailpat = re.compile(r'([-+,.\w]+@[-+.\w]+)') + +# Argh! This pattern is buggy, and will choke on URLs with GET parameters. +urlpat = re.compile(r'(\w+://[^>)\s]+)') # URLs in text + +# Blank lines +blankpat = re.compile(r'^\s*$') + +# Starting directive +htmlpat = re.compile(r'^\s*\s*$', re.IGNORECASE) +# Ending directive +nohtmlpat = re.compile(r'^\s*\s*$', re.IGNORECASE) +# Match quoted text +quotedpat = re.compile(r'^([>|:]|>)+') + + + +# Like Utils.maketext() but with caching to improve performance. +# +# _templatefilepathcache is used to associate a (templatefile, lang, listname) +# key with the file system path to a template file. This path is the one that +# the Utils.findtext() function has computed is the one to match the values in +# the key tuple. +# +# _templatecache associate a file system path as key with the text +# returned after processing the contents of that file by Utils.findtext() +# +# We keep two caches to reduce the amount of template text kept in memory, +# since the _templatefilepathcache is a many->one mapping and _templatecache +# is a one->one mapping. Imagine 1000 lists all using the same default +# English template. + +_templatefilepathcache = {} +_templatecache = {} + +def quick_maketext(templatefile, dict=None, lang=None, mlist=None): + if mlist is None: + listname = '' + else: + listname = mlist._internal_name + if lang is None: + if mlist is None: + lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + else: + lang = mlist.preferred_language + cachekey = (templatefile, lang, listname) + filepath = _templatefilepathcache.get(cachekey) + if filepath: + template = _templatecache.get(filepath) + if filepath is None or template is None: + # Use the basic maketext, with defaults to get the raw template + template, filepath = Utils.findtext(templatefile, lang=lang, + raw=True, mlist=mlist) + _templatefilepathcache[cachekey] = filepath + _templatecache[filepath] = template + # Copied from Utils.maketext() + text = template + if dict is not None: + try: + sdict = SafeDict(dict) + try: + text = sdict.interpolate(template) + except UnicodeError: + # Try again after coercing the template to unicode + utemplate = unicode(template, + Utils.GetCharSet(lang), + 'replace') + text = sdict.interpolate(utemplate) + except (TypeError, ValueError): + # The template is really screwed up + pass + # Make sure the text is in the given character set, or html-ify any bogus + # characters. + return Utils.uncanonstr(text, lang) + + + +# Note: I'm overriding most, if not all of the pipermail Article class +# here -ddm +# The Article class encapsulates a single posting. The attributes are: +# +# sequence : Sequence number, unique for each article in a set of archives +# subject : Subject +# datestr : The posting date, in human-readable format +# date : The posting date, in purely numeric format +# fromdate : The posting date, in `unixfrom' format +# headers : Any other headers of interest +# author : The author's name (and possibly organization) +# email : The author's e-mail address +# msgid : A unique message ID +# in_reply_to : If !="", this is the msgid of the article being replied to +# references: A (possibly empty) list of msgid's of earlier articles in +# the thread +# body : A list of strings making up the message body + +class Article(pipermail.Article): + __super_init = pipermail.Article.__init__ + __super_set_date = pipermail.Article._set_date + + _last_article_time = time.time() + + def __init__(self, message=None, sequence=0, keepHeaders=[], + lang=mm_cfg.DEFAULT_SERVER_LANGUAGE, mlist=None): + self.__super_init(message, sequence, keepHeaders) + self.prev = None + self.next = None + # Trim Re: from the subject line + i = 0 + while i != -1: + result = REpat.match(self.subject) + if result: + i = result.end(0) + self.subject = self.subject[i:] + else: + i = -1 + # Useful to keep around + self._lang = lang + self._mlist = mlist + + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + # Avoid i18n side-effects. Note that the language for this + # article (for this list) could be different from the site-wide + # preferred language, so we need to ensure no side-effects will + # occur. Think what happens when executing bin/arch. + otrans = i18n.get_translation() + try: + i18n.set_language(lang) + if self.author == self.email: + self.author = self.email = re.sub('@', _(' at '), + self.email) + else: + self.email = re.sub('@', _(' at '), self.email) + finally: + i18n.set_translation(otrans) + + # Snag the content-* headers. RFC 1521 states that their values are + # case insensitive. + ctype = message.get('Content-Type', 'text/plain') + cenc = message.get('Content-Transfer-Encoding', '') + self.ctype = ctype.lower() + self.cenc = cenc.lower() + self.decoded = {} + cset = Utils.GetCharSet(mlist.preferred_language) + cset_out = Charset(cset).output_charset or cset + charset = message.get_content_charset(cset_out) + if charset: + charset = charset.lower().strip() + if charset[0]=='"' and charset[-1]=='"': + charset = charset[1:-1] + if charset[0]=="'" and charset[-1]=="'": + charset = charset[1:-1] + try: + body = message.get_payload(decode=True) + except binascii.Error: + body = None + if body and charset != Utils.GetCharSet(self._lang): + # decode body + try: + body = unicode(body, charset) + except (UnicodeError, LookupError): + body = None + if body: + self.body = [l + "\n" for l in body.splitlines()] + + self.decode_headers() + + # Mapping of listnames to MailList instances as a weak value dictionary. + # This code is copied from Runner.py but there's one important operational + # difference. In Runner.py, we always .Load() the MailList object for + # each _dispose() run, otherwise the object retrieved from the cache won't + # be up-to-date. Since we're creating a new HyperArchive instance for + # each message being archived, we don't need to worry about that -- but it + # does mean there are additional opportunities for optimization. + _listcache = weakref.WeakValueDictionary() + + def _open_list(self, listname): + # Cache the open list so that any use of the list within this process + # uses the same object. We use a WeakValueDictionary so that when the + # list is no longer necessary, its memory is freed. + mlist = self._listcache.get(listname) + if not mlist: + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + syslog('error', 'error opening list: %s\n%s', listname, e) + return None + else: + self._listcache[listname] = mlist + return mlist + + def __getstate__(self): + d = self.__dict__.copy() + # We definitely don't want to pickle the MailList instance, so just + # pickle a reference to it. + if d.has_key('_mlist'): + mlist = d['_mlist'] + del d['_mlist'] + else: + mlist = None + if mlist: + d['__listname'] = self._mlist.internal_name() + else: + d['__listname'] = None + # Delete a few other things we don't want in the pickle + for attr in ('prev', 'next', 'body'): + if d.has_key(attr): + del d[attr] + d['body'] = [] + return d + + def __setstate__(self, d): + # For loading older Articles via pickle. All this stuff was added + # when Simone Piunni and Tokio Kikuchi i18n'ified Pipermail. See SF + # patch #594771. + self.__dict__ = d + listname = d.get('__listname') + if listname: + del d['__listname'] + d['_mlist'] = self._open_list(listname) + if not d.has_key('_lang'): + if hasattr(self, '_mlist'): + self._lang = self._mlist.preferred_language + else: + self._lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + if not d.has_key('cenc'): + self.cenc = None + if not d.has_key('decoded'): + self.decoded = {} + + def setListIfUnset(self, mlist): + if getattr(self, '_mlist', None) is None: + self._mlist = mlist + + def quote(self, buf): + return html_quote(buf, self._lang) + + def decode_headers(self): + """MIME-decode headers. + + If the email, subject, or author attributes contain non-ASCII + characters using the encoded-word syntax of RFC 2047, decoded versions + of those attributes are placed in the self.decoded (a dictionary). + + If the list's charset differs from the header charset, an attempt is + made to decode the headers as Unicode. If that fails, they are left + undecoded. + """ + author = self.decode_charset(self.author) + subject = self.decode_charset(self.subject) + if author: + self.decoded['author'] = author + email = self.decode_charset(self.email) + if email: + self.decoded['email'] = email + if subject: + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + otrans = i18n.get_translation() + try: + i18n.set_language(self._lang) + atmark = unicode(_(' at '), Utils.GetCharSet(self._lang)) + subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)', + '\g<1>' + atmark + '\g<2>', subject) + finally: + i18n.set_translation(otrans) + self.decoded['subject'] = subject + self.decoded['stripped'] = self.strip_subject(subject or self.subject) + + def strip_subject(self, subject): + # Strip subject_prefix and Re: for subject sorting + # This part was taken from CookHeaders.py (TK) + prefix = self._mlist.subject_prefix.strip() + if prefix: + prefix_pat = re.escape(prefix) + prefix_pat = '%'.join(prefix_pat.split(r'\%')) + prefix_pat = re.sub(r'%\d*d', r'\s*\d+\s*', prefix_pat) + subject = re.sub(prefix_pat, '', subject) + subject = subject.lstrip() + strip_pat = re.compile('^((RE|AW|SV|VS)(\[\d+\])?:\s*)+', re.I) + stripped = strip_pat.sub('', subject) + return stripped + + def decode_charset(self, field): + # TK: This function was rewritten for unifying to Unicode. + # Convert 'field' into Unicode one line string. + try: + pairs = decode_header(field) + ustr = make_header(pairs).__unicode__() + except (LookupError, UnicodeError, ValueError, HeaderParseError): + # assume list's language + cset = Utils.GetCharSet(self._mlist.preferred_language) + if cset == 'us-ascii': + cset = 'iso-8859-1' # assume this for English list + ustr = unicode(field, cset, 'replace') + return u''.join(ustr.splitlines()) + + def as_html(self): + d = self.__dict__.copy() + # avoid i18n side-effects + otrans = i18n.get_translation() + i18n.set_language(self._lang) + try: + d["prev"], d["prev_wsubj"] = self._get_prev() + d["next"], d["next_wsubj"] = self._get_next() + + d["email_html"] = self.quote(self.email) + d["title"] = self.quote(self.subject) + d["subject_html"] = self.quote(self.subject) + d["subject_url"] = url_quote(self.subject) + d["in_reply_to_url"] = url_quote(self._message_id) + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + # Point the mailto url back to the list + author = re.sub('@', _(' at '), self.author) + emailurl = self._mlist.GetListEmail() + else: + author = self.author + emailurl = self.email + d["author_html"] = self.quote(author) + d["email_url"] = url_quote(emailurl) + d["datestr_html"] = self.quote(i18n.ctime(int(self.date))) + d["body"] = self._get_body() + d["listurl"] = self._mlist.GetScriptURL('listinfo', absolute=1) + d["listname"] = self._mlist.real_name + d["encoding"] = '' + + # Links on header section (errormsg) + d["listadmin_link"] = Link(Utils.ScriptURL('admin'), _('Administration')).Format() + d["listinfo_link"] = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() + d["errormsg_header"] = _('Mailing Lists') + + finally: + i18n.set_translation(otrans) + + charset = Utils.GetCharSet(self._lang) + d["encoding"] = html_charset % charset + + self._add_decoded(d) + return quick_maketext( + 'article.html', d, + lang=self._lang, mlist=self._mlist) + + def _get_prev(self): + """Return the href and subject for the previous message""" + if self.prev: + subject = self._get_subject_enc(self.prev) + prev = ('' + % (url_quote(self.prev.filename))) + prev_wsubj = ('
  • ' + _('Previous message:') + + ' %s\n
  • ' + % (url_quote(self.prev.filename), + self.quote(subject))) + else: + prev = prev_wsubj = "" + return prev, prev_wsubj + + def _get_subject_enc(self, art): + """Return the subject of art, decoded if possible. + + If the charset of the current message and art match and the + article's subject is encoded, decode it. + """ + return art.decoded.get('subject', art.subject) + + def _get_next(self): + """Return the href and subject for the previous message""" + if self.next: + subject = self._get_subject_enc(self.next) + next = ('' + % (url_quote(self.next.filename))) + next_wsubj = ('
  • ' + _('Next message:') + + ' %s\n
  • ' + % (url_quote(self.next.filename), + self.quote(subject))) + else: + next = next_wsubj = "" + return next, next_wsubj + + _rx_quote = re.compile('=([A-F0-9][A-F0-9])') + _rx_softline = re.compile('=[ \t]*$') + + def _get_body(self): + """Return the message body ready for HTML, decoded if necessary""" + try: + body = self.html_body + except AttributeError: + body = self.body + return null_to_space(EMPTYSTRING.join(body)) + + def _add_decoded(self, d): + """Add encoded-word keys to HTML output""" + for src, dst in (('author', 'author_html'), + ('email', 'email_html'), + ('subject', 'subject_html'), + ('subject', 'title')): + if self.decoded.has_key(src): + d[dst] = self.quote(self.decoded[src]) + + def as_text(self): + d = self.__dict__.copy() + # We need to guarantee a valid From_ line, even if there are + # bososities in the headers. + if not d.get('fromdate', '').strip(): + d['fromdate'] = time.ctime(time.time()) + if not d.get('email', '').strip(): + d['email'] = 'bogus@does.not.exist.com' + if not d.get('datestr', '').strip(): + d['datestr'] = time.ctime(time.time()) + # + headers = ['From %(email)s %(fromdate)s', + 'From: %(email)s (%(author)s)', + 'Date: %(datestr)s', + 'Subject: %(subject)s'] + if d['_in_reply_to']: + headers.append('In-Reply-To: %(_in_reply_to)s') + if d['_references']: + headers.append('References: %(_references)s') + if d['_message_id']: + headers.append('Message-ID: %(_message_id)s') + body = EMPTYSTRING.join(self.body) + cset = Utils.GetCharSet(self._lang) + # Coerce the body to Unicode and replace any invalid characters. + if not isinstance(body, types.UnicodeType): + body = unicode(body, cset, 'replace') + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + otrans = i18n.get_translation() + try: + atmark = unicode(_(' at '), cset) + i18n.set_language(self._lang) + body = re.sub(r'([-+,.\w]+)@([-+.\w]+)', + '\g<1>' + atmark + '\g<2>', body) + finally: + i18n.set_translation(otrans) + # Return body to character set of article. + body = body.encode(cset, 'replace') + return NL.join(headers) % d + '\n\n' + body + '\n' + + def _set_date(self, message): + self.__super_set_date(message) + self.fromdate = time.ctime(int(self.date)) + + def loadbody_fromHTML(self,fileobj): + self.body = [] + begin = 0 + while 1: + line = fileobj.readline() + if not line: + break + if not begin: + if line.strip() == '': + begin = 1 + continue + if line.strip() == '': + break + self.body.append(line) + + def finished_update_article(self): + self.body = [] + try: + del self.html_body + except AttributeError: + pass + + +class HyperArchive(pipermail.T): + __super_init = pipermail.T.__init__ + __super_update_archive = pipermail.T.update_archive + __super_update_dirty_archives = pipermail.T.update_dirty_archives + __super_add_article = pipermail.T.add_article + + # some defaults + DIRMODE = 02775 + FILEMODE = 0660 + + VERBOSE = 0 + DEFAULTINDEX = 'thread' + ARCHIVE_PERIOD = 'month' + + THREADLAZY = 0 + THREADLEVELS = 3 + + ALLOWHTML = 1 # "Lines between " handled as is. + SHOWHTML = 0 # Eg, nuke leading whitespace in html manner. + IQUOTES = 1 # Italicize quoted text. + SHOWBR = 0 # Add
    onto every line + + def __init__(self, maillist): + # can't init the database while other processes are writing to it! + # XXX TODO- implement native locking + # with mailman's LockFile module for HyperDatabase.HyperDatabase + # + dir = maillist.archive_dir() + db = HyperDatabase.HyperDatabase(dir, maillist) + self.__super_init(dir, reload=1, database=db) + + self.maillist = maillist + self._lock_file = None + self.lang = maillist.preferred_language + self.charset = Utils.GetCharSet(maillist.preferred_language) + + if hasattr(self.maillist,'archive_volume_frequency'): + if self.maillist.archive_volume_frequency == 0: + self.ARCHIVE_PERIOD='year' + elif self.maillist.archive_volume_frequency == 2: + self.ARCHIVE_PERIOD='quarter' + elif self.maillist.archive_volume_frequency == 3: + self.ARCHIVE_PERIOD='week' + elif self.maillist.archive_volume_frequency == 4: + self.ARCHIVE_PERIOD='day' + else: + self.ARCHIVE_PERIOD='month' + + yre = r'(?P[0-9]{4,4})' + mre = r'(?P[01][0-9])' + dre = r'(?P[0123][0-9])' + self._volre = { + 'year': '^' + yre + '$', + 'quarter': '^' + yre + r'q(?P[1234])$', + 'month': '^' + yre + r'-(?P[a-zA-Z]+)$', + 'week': r'^Week-of-Mon-' + yre + mre + dre, + 'day': '^' + yre + mre + dre + '$' + } + + def _makeArticle(self, msg, sequence): + return Article(msg, sequence, + lang=self.maillist.preferred_language, + mlist=self.maillist) + + def html_foot(self): + # avoid i18n side-effects + mlist = self.maillist + otrans = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + # Convenience + def quotetime(s): + return html_quote(i18n.ctime(s), self.lang) + try: + d = {"lastdate": quotetime(self.lastdate), + "archivedate": quotetime(self.archivedate), + "listinfo": mlist.GetScriptURL('listinfo', absolute=1), + "version": self.version, + } + i = {"thread": _("thread"), + "subject": _("subject"), + "author": _("author"), + "date": _("date") + } + finally: + i18n.set_translation(otrans) + + for t in i.keys(): + cap = t[0].upper() + t[1:] + if self.type == cap: + d["%s_ref" % (t)] = "" + else: + d["%s_ref" % (t)] = ('[ %s ]' + % (t, i[t])) + return quick_maketext( + 'archidxfoot.html', d, + mlist=mlist) + + def html_head(self): + # avoid i18n side-effects + mlist = self.maillist + otrans = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + # Convenience + def quotetime(s): + return html_quote(i18n.ctime(s), self.lang) + + try: + d = {"listname": html_quote(mlist.real_name, self.lang), + "archtype": self.type, + "archive": self.volNameToDesc(self.archive), + "listinfo": mlist.GetScriptURL('listinfo', absolute=1), + "firstdate": quotetime(self.firstdate), + "lastdate": quotetime(self.lastdate), + "size": self.size, + } + i = {"thread": _("thread"), + "subject": _("subject"), + "author": _("author"), + "date": _("date"), + } + finally: + i18n.set_translation(otrans) + + for t in i.keys(): + cap = t[0].upper() + t[1:] + if self.type == cap: + d["%s_ref" % (t)] = "" + d["archtype"] = i[t] + else: + d["%s_ref" % (t)] = ('[ %s ]' + % (t, i[t])) + if self.charset: + d["encoding"] = html_charset % self.charset + else: + d["encoding"] = "" + + # Links on header section (errormsg) + d["listadmin_link"] = Link(Utils.ScriptURL('admin'), _('Administration')).Format() + d["listinfo_link"] = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() + d["errormsg_header"] = _('Mailing Lists') + + return quick_maketext( + 'archidxhead.html', d, + mlist=mlist) + + def html_TOC(self): + mlist = self.maillist + listname = mlist.internal_name() + mbox = os.path.join(mlist.archive_dir()+'.mbox', listname+'.mbox') + d = {"listname": mlist.real_name, + "listinfo": mlist.GetScriptURL('listinfo', absolute=1), + "fullarch": '../%s.mbox/%s.mbox' % (listname, listname), + "size": sizeof(mbox, mlist.preferred_language), + 'meta': '', + } + # Avoid i18n side-effects + otrans = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + try: + if not self.archives: + d["noarchive_msg"] = _( + 'Currently, there are no archives.') + d["archive_listing_start"] = "" + d["archive_listing_end"] = "" + d["archive_listing"] = "" + else: + d["noarchive_msg"] = "" + d["archive_listing_start"] = quick_maketext( + 'archliststart.html', + lang=mlist.preferred_language, + mlist=mlist) + d["archive_listing_end"] = quick_maketext( + 'archlistend.html', + mlist=mlist) + + accum = [] + highlight = 1 + for a in self.archives: + # Highlight TOC's rows + if highlight == 1: + css = 'class="title"' + highlight = 0 + else: + css = '' + highlight = 1 + accum.append(self.html_TOC_entry(a, css)) + d["archive_listing"] = EMPTYSTRING.join(accum) + finally: + i18n.set_translation(otrans) + # The TOC is always in the charset of the list's preferred language + d['meta'] += html_charset % Utils.GetCharSet(mlist.preferred_language) + # Links on header section (errormsg) + d['listadmin_link'] = Link(Utils.ScriptURL('admin'), _('Administration')).Format() + d['listinfo_link'] = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() + d['errormsg_header'] = _('Mailing Lists') + # The site can disable public access to the mbox file. + if mm_cfg.PUBLIC_MBOX: + template = 'archtoc.html' + else: + template = 'archtocnombox.html' + return quick_maketext(template, d, mlist=mlist) + + def html_TOC_entry(self, arch, css): + # Check to see if the archive is gzip'd or not + txtfile = os.path.join(self.maillist.archive_dir(), arch + '.txt') + gzfile = txtfile + '.gz' + # which exists? .txt.gz first, then .txt + if os.path.exists(gzfile): + file = gzfile + url = arch + '.txt.gz' + templ = '[ ' + _('Gzip\'d Text%(sz)s') \ + + ']' + elif os.path.exists(txtfile): + file = txtfile + url = arch + '.txt' + templ = '[ ' + _('Text%(sz)s') + ']' + else: + # neither found? + file = None + # in Python 1.5.2 we have an easy way to get the size + if file: + textlink = templ % { + 'url': url, + 'sz' : sizeof(file, self.maillist.preferred_language) + } + else: + # there's no archive file at all... hmmm. + textlink = '' + return quick_maketext( + 'archtocentry.html', + {'archive': arch, + 'archivelabel': self.volNameToDesc(arch), + 'css': css, + 'textlink': textlink + }, + mlist=self.maillist) + + def GetArchLock(self): + if self._lock_file: + return 1 + self._lock_file = LockFile.LockFile( + os.path.join(mm_cfg.LOCK_DIR, + self.maillist.internal_name() + '-arch.lock')) + try: + self._lock_file.lock(timeout=0.5) + except LockFile.TimeOutError: + return 0 + return 1 + + def DropArchLock(self): + if self._lock_file: + self._lock_file.unlock(unconditionally=1) + self._lock_file = None + + def processListArch(self): + name = self.maillist.ArchiveFileName() + wname= name+'.working' + ename= name+'.err_unarchived' + try: + os.stat(name) + except (IOError,os.error): + #no archive file, nothin to do -ddm + return + + #see if arch is locked here -ddm + if not self.GetArchLock(): + #another archiver is running, nothing to do. -ddm + return + + #if the working file is still here, the archiver may have + # crashed during archiving. Save it, log an error, and move on. + try: + wf = open(wname) + syslog('error', + 'Archive working file %s present. ' + 'Check %s for possibly unarchived msgs', + wname, ename) + omask = os.umask(007) + try: + ef = open(ename, 'a+') + finally: + os.umask(omask) + ef.seek(1,2) + if ef.read(1) <> '\n': + ef.write('\n') + ef.write(wf.read()) + ef.close() + wf.close() + os.unlink(wname) + except IOError: + pass + os.rename(name,wname) + archfile = open(wname) + self.processUnixMailbox(archfile) + archfile.close() + os.unlink(wname) + self.DropArchLock() + + def get_filename(self, article): + return '%06i.html' % (article.sequence,) + + def get_archives(self, article): + """Return a list of indexes where the article should be filed. + A string can be returned if the list only contains one entry, + and the empty list is legal.""" + res = self.dateToVolName(float(article.date)) + self.message(_("figuring article archives\n")) + self.message(res + "\n") + return res + + def volNameToDesc(self, volname): + volname = volname.strip() + # Don't make these module global constants since we have to runtime + # translate them anyway. + monthdict = [ + '', + _('January'), _('February'), _('March'), _('April'), + _('May'), _('June'), _('July'), _('August'), + _('September'), _('October'), _('November'), _('December') + ] + for each in self._volre.keys(): + match = re.match(self._volre[each], volname) + # Let ValueErrors percolate up + if match: + year = int(match.group('year')) + if each == 'quarter': + d =["", _("First"), _("Second"), _("Third"), _("Fourth") ] + ord = d[int(match.group('quarter'))] + return _("%(ord)s quarter %(year)i") + elif each == 'month': + monthstr = match.group('month').lower() + for i in range(1, 13): + monthname = time.strftime("%B", (1999,i,1,0,0,0,0,1,0)) + if monthstr.lower() == monthname.lower(): + month = monthdict[i] + return _("%(month)s %(year)i") + raise ValueError, "%s is not a month!" % monthstr + elif each == 'week': + month = monthdict[int(match.group("month"))] + day = int(match.group("day")) + return _("The Week Of Monday %(day)i %(month)s %(year)i") + elif each == 'day': + month = monthdict[int(match.group("month"))] + day = int(match.group("day")) + return _("%(day)i %(month)s %(year)i") + else: + return match.group('year') + raise ValueError, "%s is not a valid volname" % volname + +# The following two methods should be inverses of each other. -ddm + + def dateToVolName(self,date): + datetuple=time.localtime(date) + if self.ARCHIVE_PERIOD=='year': + return time.strftime("%Y",datetuple) + elif self.ARCHIVE_PERIOD=='quarter': + if datetuple[1] in [1,2,3]: + return time.strftime("%Yq1",datetuple) + elif datetuple[1] in [4,5,6]: + return time.strftime("%Yq2",datetuple) + elif datetuple[1] in [7,8,9]: + return time.strftime("%Yq3",datetuple) + else: + return time.strftime("%Yq4",datetuple) + elif self.ARCHIVE_PERIOD == 'day': + return time.strftime("%Y%m%d", datetuple) + elif self.ARCHIVE_PERIOD == 'week': + # Reconstruct "seconds since epoch", and subtract weekday + # multiplied by the number of seconds in a day. + monday = time.mktime(datetuple) - datetuple[6] * 24 * 60 * 60 + # Build a new datetuple from this "seconds since epoch" value + datetuple = time.localtime(monday) + return time.strftime("Week-of-Mon-%Y%m%d", datetuple) + # month. -ddm + else: + return time.strftime("%Y-%B",datetuple) + + + def volNameToDate(self, volname): + volname = volname.strip() + for each in self._volre.keys(): + match = re.match(self._volre[each],volname) + if match: + year = int(match.group('year')) + month = 1 + day = 1 + if each == 'quarter': + q = int(match.group('quarter')) + month = (q * 3) - 2 + elif each == 'month': + monthstr = match.group('month').lower() + m = [] + for i in range(1,13): + m.append( + time.strftime("%B",(1999,i,1,0,0,0,0,1,0)).lower()) + try: + month = m.index(monthstr) + 1 + except ValueError: + pass + elif each == 'week' or each == 'day': + month = int(match.group("month")) + day = int(match.group("day")) + try: + return time.mktime((year,month,1,0,0,0,0,1,-1)) + except OverflowError: + return 0.0 + return 0.0 + + def sortarchives(self): + def sf(a, b): + al = self.volNameToDate(a) + bl = self.volNameToDate(b) + if al > bl: + return 1 + elif al < bl: + return -1 + else: + return 0 + if self.ARCHIVE_PERIOD in ('month','year','quarter'): + self.archives.sort(sf) + else: + self.archives.sort() + self.archives.reverse() + + def message(self, msg): + if self.VERBOSE: + f = sys.stderr + f.write(msg) + if msg[-1:] != '\n': + f.write('\n') + f.flush() + + def open_new_archive(self, archive, archivedir): + index_html = os.path.join(archivedir, 'index.html') + try: + os.unlink(index_html) + except: + pass + os.symlink(self.DEFAULTINDEX+'.html',index_html) + + def write_index_header(self): + self.depth=0 + print self.html_head() + if not self.THREADLAZY and self.type=='Thread': + self.message(_("Computing threaded index\n")) + self.updateThreadedIndex() + + def write_index_footer(self): + for i in range(self.depth): + print '' + print self.html_foot() + + def write_index_entry(self, article): + subject = self.get_header("subject", article) + author = self.get_header("author", article) + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + try: + author = re.sub('@', _(' at '), author) + except UnicodeError: + # Non-ASCII author contains '@' ... no valid email anyway + pass + subject = CGIescape(subject, self.lang) + author = CGIescape(author, self.lang) + + d = { + 'filename': urllib.quote(article.filename), + 'subject': subject, + 'sequence': article.sequence, + 'author': author + } + print quick_maketext( + 'archidxentry.html', d, + mlist=self.maillist) + + def get_header(self, field, article): + # if we have no decoded header, return the encoded one + result = article.decoded.get(field) + if result is None: + return getattr(article, field) + # otherwise, the decoded one will be Unicode + return result + + def write_threadindex_entry(self, article, depth): + if depth < 0: + self.message('depth<0') + depth = 0 + if depth > self.THREADLEVELS: + depth = self.THREADLEVELS + if depth < self.depth: + for i in range(self.depth-depth): + print '' + elif depth > self.depth: + for i in range(depth-self.depth): + print '
      ' + print '' % (depth, article.threadKey) + self.depth = depth + self.write_index_entry(article) + + def write_TOC(self): + self.sortarchives() + omask = os.umask(002) + try: + toc = open(os.path.join(self.basedir, 'index.html'), 'w') + finally: + os.umask(omask) + toc.write(self.html_TOC()) + toc.close() + + def write_article(self, index, article, path): + # called by add_article + omask = os.umask(002) + try: + f = open(path, 'w') + finally: + os.umask(omask) + f.write(article.as_html()) + f.close() + + # Write the text article to the text archive. + path = os.path.join(self.basedir, "%s.txt" % index) + omask = os.umask(002) + try: + f = open(path, 'a+') + finally: + os.umask(omask) + f.write(article.as_text()) + f.close() + + def update_archive(self, archive): + self.__super_update_archive(archive) + # only do this if the gzip module was imported globally, and + # gzip'ing was enabled via mm_cfg.GZIP_ARCHIVE_TXT_FILES. See + # above. + if gzip: + archz = None + archt = None + txtfile = os.path.join(self.basedir, '%s.txt' % archive) + gzipfile = os.path.join(self.basedir, '%s.txt.gz' % archive) + oldgzip = os.path.join(self.basedir, '%s.old.txt.gz' % archive) + try: + # open the plain text file + archt = open(txtfile) + except IOError: + return + try: + os.rename(gzipfile, oldgzip) + archz = gzip.open(oldgzip) + except (IOError, RuntimeError, os.error): + pass + try: + ou = os.umask(002) + newz = gzip.open(gzipfile, 'w') + finally: + # XXX why is this a finally? + os.umask(ou) + if archz: + newz.write(archz.read()) + archz.close() + os.unlink(oldgzip) + # XXX do we really need all this in a try/except? + try: + newz.write(archt.read()) + newz.close() + archt.close() + except IOError: + pass + os.unlink(txtfile) + + _skip_attrs = ('maillist', '_lock_file', 'charset') + + def getstate(self): + d={} + for each in self.__dict__.keys(): + if not (each in self._skip_attrs + or each.upper() == each): + d[each] = self.__dict__[each] + return d + + # Add tags around URLs and e-mail addresses. + + def __processbody_URLquote(self, lines): + # XXX a lot to do here: + # 1. use lines directly, rather than source and dest + # 2. make it clearer + # 3. make it faster + # TK: Prepare for unicode obscure. + atmark = _(' at ') + if lines and isinstance(lines[0], types.UnicodeType): + atmark = unicode(atmark, Utils.GetCharSet(self.lang), 'replace') + source = lines[:] + dest = lines + last_line_was_quoted = 0 + for i in xrange(0, len(source)): + Lorig = L = source[i] + prefix = suffix = "" + if L is None: + continue + # Italicise quoted text + if self.IQUOTES: + quoted = quotedpat.match(L) + if quoted is None: + last_line_was_quoted = 0 + else: + quoted = quoted.end(0) + prefix = CGIescape(L[:quoted], self.lang) + '' + suffix = '' + if self.SHOWHTML: + suffix += '
      ' + if not last_line_was_quoted: + prefix = '
      ' + prefix + L = L[quoted:] + last_line_was_quoted = 1 + # Check for an e-mail address + L2 = "" + jr = emailpat.search(L) + kr = urlpat.search(L) + while jr is not None or kr is not None: + if jr == None: + j = -1 + else: + j = jr.start(0) + if kr is None: + k = -1 + else: + k = kr.start(0) + if j != -1 and (j < k or k == -1): + text = jr.group(1) + length = len(text) + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + text = re.sub('@', atmark, text) + URL = self.maillist.GetScriptURL( + 'listinfo', absolute=1) + else: + URL = 'mailto:' + text + pos = j + elif k != -1 and (j > k or j == -1): + text = URL = kr.group(1) + length = len(text) + pos = k + else: # j==k + raise ValueError, "j==k: This can't happen!" + #length = len(text) + #self.message("URL: %s %s %s \n" + # % (CGIescape(L[:pos]), URL, CGIescape(text))) + L2 += '%s
      %s' % ( + CGIescape(L[:pos], self.lang), + html_quote(URL), CGIescape(text, self.lang)) + L = L[pos+length:] + jr = emailpat.search(L) + kr = urlpat.search(L) + if jr is None and kr is None: + L = CGIescape(L, self.lang) + L = prefix + L2 + L + suffix + source[i] = None + dest[i] = L + + # Perform Hypermail-style processing of directives + # in message bodies. Lines between and will be written + # out precisely as they are; other lines will be passed to func2 + # for further processing . + + def __processbody_HTML(self, lines): + # XXX need to make this method modify in place + source = lines[:] + dest = lines + l = len(source) + i = 0 + while i < l: + while i < l and htmlpat.match(source[i]) is None: + i = i + 1 + if i < l: + source[i] = None + i = i + 1 + while i < l and nohtmlpat.match(source[i]) is None: + dest[i], source[i] = source[i], None + i = i + 1 + if i < l: + source[i] = None + i = i + 1 + + def format_article(self, article): + # called from add_article + # TBD: Why do the HTML formatting here and keep it in the + # pipermail database? It makes more sense to do the html + # formatting as the article is being written as html and toss + # the data after it has been written to the archive file. + lines = filter(None, article.body) + # Handle directives + if self.ALLOWHTML: + self.__processbody_HTML(lines) + self.__processbody_URLquote(lines) + if not self.SHOWHTML and lines: + lines.insert(0, '
      ')
      +            lines.append('
      ') + else: + # Do fancy formatting here + if self.SHOWBR: + lines = map(lambda x:x + "
      ", lines) + else: + for i in range(0, len(lines)): + s = lines[i] + if s[0:1] in ' \t\n': + lines[i] = '

      ' + s + article.html_body = lines + return article + + def update_article(self, arcdir, article, prev, next): + seq = article.sequence + filename = os.path.join(arcdir, article.filename) + self.message(_('Updating HTML for article %(seq)s')) + try: + f = open(filename) + article.loadbody_fromHTML(f) + f.close() + except IOError, e: + if e.errno <> errno.ENOENT: raise + self.message(_('article file %(filename)s is missing!')) + article.prev = prev + article.next = next + omask = os.umask(002) + try: + f = open(filename, 'w') + finally: + os.umask(omask) + f.write(article.as_html()) + f.close() diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/pipermail.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/pipermail.py new file mode 100644 index 0000000..8b20b56 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Archiver/pipermail.py @@ -0,0 +1,879 @@ +#! /usr/bin/env python + +from __future__ import nested_scopes + +import mailbox +import os +import re +import sys +import time +from email.Utils import parseaddr, parsedate_tz, mktime_tz, formatdate +import cPickle as pickle +from cStringIO import StringIO +from string import lowercase + +__version__ = '0.09 (Mailman edition)' +VERSION = __version__ +CACHESIZE = 100 # Number of slots in the cache + +from Mailman import Errors +from Mailman.Mailbox import ArchiverMailbox +from Mailman.Logging.Syslog import syslog +from Mailman.i18n import _ + +# True/False +try: + True, False +except NameError: + True = 1 + False = 0 + +SPACE = ' ' + + +msgid_pat = re.compile(r'(<.*>)') +def strip_separators(s): + "Remove quotes or parenthesization from a Message-ID string" + if not s: + return "" + if s[0] in '"<([' and s[-1] in '">)]': + s = s[1:-1] + return s + +smallNameParts = ['van', 'von', 'der', 'de'] + +def fixAuthor(author): + "Canonicalize a name into Last, First format" + # If there's a comma, guess that it's already in "Last, First" format + if ',' in author: + return author + L = author.split() + i = len(L) - 1 + if i == 0: + return author # The string's one word--forget it + if author.upper() == author or author.lower() == author: + # Damn, the name is all upper- or lower-case. + while i > 0 and L[i-1].lower() in smallNameParts: + i = i - 1 + else: + # Mixed case; assume that small parts of the last name will be + # in lowercase, and check them against the list. + while i>0 and (L[i-1][0] in lowercase or + L[i-1].lower() in smallNameParts): + i = i - 1 + author = SPACE.join(L[-1:] + L[i:-1]) + ', ' + SPACE.join(L[:i]) + return author + +# Abstract class for databases + +class DatabaseInterface: + def __init__(self): pass + def close(self): pass + def getArticle(self, archive, msgid): pass + def hasArticle(self, archive, msgid): pass + def addArticle(self, archive, article, subject=None, author=None, + date=None): pass + def firstdate(self, archive): pass + def lastdate(self, archive): pass + def first(self, archive, index): pass + def next(self, archive, index): pass + def numArticles(self, archive): pass + def newArchive(self, archive): pass + def setThreadKey(self, archive, key, msgid): pass + def getOldestArticle(self, subject): pass + +class Database(DatabaseInterface): + """Define the basic sorting logic for a database + + Assumes that the database internally uses dateIndex, authorIndex, + etc. + """ + + # TBD Factor out more of the logic shared between BSDDBDatabase + # and HyperDatabase and place it in this class. + + def __init__(self): + # This method need not be called by subclasses that do their + # own initialization. + self.dateIndex = {} + self.authorIndex = {} + self.subjectIndex = {} + self.articleIndex = {} + self.changed = {} + + def addArticle(self, archive, article, subject=None, author=None, + date=None): + # create the keys; always end w/ msgid which will be unique + authorkey = (author or article.author, article.date, + article.msgid) + subjectkey = (subject or article.subject, article.date, + article.msgid) + datekey = date or article.date, article.msgid + + # Add the new article + self.dateIndex[datekey] = article.msgid + self.authorIndex[authorkey] = article.msgid + self.subjectIndex[subjectkey] = article.msgid + + self.store_article(article) + self.changed[archive, article.msgid] = None + + parentID = article.parentID + if parentID is not None and self.articleIndex.has_key(parentID): + parent = self.getArticle(archive, parentID) + myThreadKey = parent.threadKey + article.date + '-' + else: + myThreadKey = article.date + '-' + article.threadKey = myThreadKey + key = myThreadKey, article.msgid + self.setThreadKey(archive, key, article.msgid) + + def store_article(self, article): + """Store article without message body to save space""" + # TBD this is not thread safe! + temp = article.body + temp2 = article.html_body + article.body = [] + del article.html_body + self.articleIndex[article.msgid] = pickle.dumps(article) + article.body = temp + article.html_body = temp2 + + +# The Article class encapsulates a single posting. The attributes +# are: +# +# sequence : Sequence number, unique for each article in a set of archives +# subject : Subject +# datestr : The posting date, in human-readable format +# date : The posting date, in purely numeric format +# headers : Any other headers of interest +# author : The author's name (and possibly organization) +# email : The author's e-mail address +# msgid : A unique message ID +# in_reply_to: If != "", this is the msgid of the article being replied to +# references : A (possibly empty) list of msgid's of earlier articles +# in the thread +# body : A list of strings making up the message body + +class Article: + _last_article_time = time.time() + + def __init__(self, message = None, sequence = 0, keepHeaders = []): + if message is None: + return + self.sequence = sequence + + self.parentID = None + self.threadKey = None + # otherwise the current sequence number is used. + id = strip_separators(message['Message-Id']) + if id == "": + self.msgid = str(self.sequence) + else: self.msgid = id + + if message.has_key('Subject'): + self.subject = str(message['Subject']) + else: + self.subject = _('No subject') + if self.subject == "": self.subject = _('No subject') + + self._set_date(message) + + # Figure out the e-mail address and poster's name. Use the From: + # field first, followed by Reply-To: + self.author, self.email = parseaddr(message.get('From', '')) + e = message['Reply-To'] + if not self.email and e is not None: + ignoreauthor, self.email = parseaddr(e) + self.email = strip_separators(self.email) + self.author = strip_separators(self.author) + + if self.author == "": + self.author = self.email + + # Save the In-Reply-To:, References:, and Message-ID: lines + # + # TBD: The original code does some munging on these fields, which + # shouldn't be necessary, but changing this may break code. For + # safety, I save the original headers on different attributes for use + # in writing the plain text periodic flat files. + self._in_reply_to = message['in-reply-to'] + self._references = message['references'] + self._message_id = message['message-id'] + + i_r_t = message['In-Reply-To'] + if i_r_t is None: + self.in_reply_to = '' + else: + match = msgid_pat.search(i_r_t) + if match is None: self.in_reply_to = '' + else: self.in_reply_to = strip_separators(match.group(1)) + + references = message['References'] + if references is None: + self.references = [] + else: + self.references = map(strip_separators, references.split()) + + # Save any other interesting headers + self.headers = {} + for i in keepHeaders: + if message.has_key(i): + self.headers[i] = message[i] + + # Read the message body + s = StringIO(message.get_payload(decode=True)\ + or message.as_string().split('\n\n',1)[1]) + self.body = s.readlines() + + def _set_date(self, message): + def floatdate(header): + missing = [] + datestr = message.get(header, missing) + if datestr is missing: + return None + date = parsedate_tz(datestr) + try: + return mktime_tz(date) + except (TypeError, ValueError, OverflowError): + return None + date = floatdate('date') + if date is None: + date = floatdate('x-list-received-date') + if date is None: + # What's left to try? + date = self._last_article_time + 1 + self._last_article_time = date + self.date = '%011i' % date + self.datestr = message.get('date') \ + or message.get('x-list-received-date') \ + or formatdate(date) + + def __repr__(self): + return '

      ' + + def finished_update_article(self): + pass + +# Pipermail formatter class + +class T: + DIRMODE = 0755 # Mode to give to created directories + FILEMODE = 0644 # Mode to give to created files + INDEX_EXT = ".html" # Extension for indexes + + def __init__(self, basedir = None, reload = 1, database = None): + # If basedir isn't provided, assume the current directory + if basedir is None: + self.basedir = os.getcwd() + else: + basedir = os.path.expanduser(basedir) + self.basedir = basedir + self.database = database + + # If the directory doesn't exist, create it. This code shouldn't get + # run anymore, we create the directory in Archiver.py. It should only + # get used by legacy lists created that are only receiving their first + # message in the HTML archive now -- Marc + try: + os.stat(self.basedir) + except os.error, errdata: + errno, errmsg = errdata + if errno != 2: + raise os.error, errdata + else: + self.message(_('Creating archive directory ') + self.basedir) + omask = os.umask(0) + try: + os.mkdir(self.basedir, self.DIRMODE) + finally: + os.umask(omask) + + # Try to load previously pickled state + try: + if not reload: + raise IOError + f = open(os.path.join(self.basedir, 'pipermail.pck'), 'r') + self.message(_('Reloading pickled archive state')) + d = pickle.load(f) + f.close() + for key, value in d.items(): + setattr(self, key, value) + except (IOError, EOFError): + # No pickled version, so initialize various attributes + self.archives = [] # Archives + self._dirty_archives = [] # Archives that will have to be updated + self.sequence = 0 # Sequence variable used for + # numbering articles + self.update_TOC = 0 # Does the TOC need updating? + # + # make the basedir variable work when passed in as an __init__ arg + # and different from the one in the pickle. Let the one passed in + # as an __init__ arg take precedence if it's stated. This way, an + # archive can be moved from one place to another and still work. + # + if basedir != self.basedir: + self.basedir = basedir + + def close(self): + "Close an archive, save its state, and update any changed archives." + self.update_dirty_archives() + self.update_TOC = 0 + self.write_TOC() + # Save the collective state + self.message(_('Pickling archive state into ') + + os.path.join(self.basedir, 'pipermail.pck')) + self.database.close() + del self.database + + omask = os.umask(007) + try: + f = open(os.path.join(self.basedir, 'pipermail.pck'), 'w') + finally: + os.umask(omask) + pickle.dump(self.getstate(), f) + f.close() + + def getstate(self): + # can override this in subclass + return self.__dict__ + + # + # Private methods + # + # These will be neither overridden nor called by custom archivers. + # + + + # Create a dictionary of various parameters that will be passed + # to the write_index_{header,footer} functions + def __set_parameters(self, archive): + # Determine the earliest and latest date in the archive + firstdate = self.database.firstdate(archive) + lastdate = self.database.lastdate(archive) + + # Get the current time + now = time.asctime(time.localtime(time.time())) + self.firstdate = firstdate + self.lastdate = lastdate + self.archivedate = now + self.size = self.database.numArticles(archive) + self.archive = archive + self.version = __version__ + + # Find the message ID of an article's parent, or return None + # if no parent can be found. + + def __findParent(self, article, children = []): + parentID = None + if article.in_reply_to: + parentID = article.in_reply_to + elif article.references: + # Remove article IDs that aren't in the archive + refs = filter(self.articleIndex.has_key, article.references) + if not refs: + return None + maxdate = self.database.getArticle(self.archive, + refs[0]) + for ref in refs[1:]: + a = self.database.getArticle(self.archive, ref) + if a.date > maxdate.date: + maxdate = a + parentID = maxdate.msgid + else: + # Look for the oldest matching subject + try: + key, tempid = \ + self.subjectIndex.set_location(article.subject) + print key, tempid + self.subjectIndex.next() + [subject, date] = key.split('\0') + print article.subject, subject, date + if subject == article.subject and tempid not in children: + parentID = tempid + except KeyError: + pass + return parentID + + # Update the threaded index completely + def updateThreadedIndex(self): + # Erase the threaded index + self.database.clearIndex(self.archive, 'thread') + + # Loop over all the articles + msgid = self.database.first(self.archive, 'date') + while msgid is not None: + try: + article = self.database.getArticle(self.archive, msgid) + except KeyError: + pass + else: + if article.parentID is None or \ + not self.database.hasArticle(self.archive, + article.parentID): + # then + pass + else: + parent = self.database.getArticle(self.archive, + article.parentID) + article.threadKey = parent.threadKey+article.date+'-' + self.database.setThreadKey(self.archive, + (article.threadKey, article.msgid), + msgid) + msgid = self.database.next(self.archive, 'date') + + # + # Public methods: + # + # These are part of the public interface of the T class, but will + # never be overridden (unless you're trying to do something very new). + + # Update a single archive's indices, whether the archive's been + # dirtied or not. + def update_archive(self, archive): + self.archive = archive + self.message(_("Updating index files for archive [%(archive)s]")) + arcdir = os.path.join(self.basedir, archive) + self.__set_parameters(archive) + + for hdr in ('Date', 'Subject', 'Author'): + self._update_simple_index(hdr, archive, arcdir) + + self._update_thread_index(archive, arcdir) + + def _update_simple_index(self, hdr, archive, arcdir): + self.message(" " + hdr) + self.type = hdr + hdr = hdr.lower() + + self._open_index_file_as_stdout(arcdir, hdr) + self.write_index_header() + count = 0 + # Loop over the index entries + msgid = self.database.first(archive, hdr) + while msgid is not None: + try: + article = self.database.getArticle(self.archive, msgid) + except KeyError: + pass + else: + count = count + 1 + self.write_index_entry(article) + msgid = self.database.next(archive, hdr) + # Finish up this index + self.write_index_footer() + self._restore_stdout() + + def _update_thread_index(self, archive, arcdir): + self.message(_(" Thread")) + self._open_index_file_as_stdout(arcdir, "thread") + self.type = 'Thread' + self.write_index_header() + + # To handle the prev./next in thread pointers, we need to + # track articles 5 at a time. + + # Get the first 5 articles + L = [None] * 5 + i = 2 + msgid = self.database.first(self.archive, 'thread') + + while msgid is not None and i < 5: + L[i] = self.database.getArticle(self.archive, msgid) + i = i + 1 + msgid = self.database.next(self.archive, 'thread') + + while L[2] is not None: + article = L[2] + artkey = None + if article is not None: + artkey = article.threadKey + if artkey is not None: + self.write_threadindex_entry(article, artkey.count('-') - 1) + if self.database.changed.has_key((archive,article.msgid)): + a1 = L[1] + a3 = L[3] + self.update_article(arcdir, article, a1, a3) + if a3 is not None: + self.database.changed[(archive, a3.msgid)] = None + if a1 is not None: + key = archive, a1.msgid + if not self.database.changed.has_key(key): + self.update_article(arcdir, a1, L[0], L[2]) + else: + del self.database.changed[key] + if L[0]: + L[0].finished_update_article() + L = L[1:] # Rotate the list + if msgid is None: + L.append(msgid) + else: + L.append(self.database.getArticle(self.archive, msgid)) + msgid = self.database.next(self.archive, 'thread') + + self.write_index_footer() + self._restore_stdout() + + def _open_index_file_as_stdout(self, arcdir, index_name): + path = os.path.join(arcdir, index_name + self.INDEX_EXT) + omask = os.umask(002) + try: + self.__f = open(path, 'w') + finally: + os.umask(omask) + self.__stdout = sys.stdout + sys.stdout = self.__f + + def _restore_stdout(self): + sys.stdout = self.__stdout + self.__f.close() + del self.__f + del self.__stdout + + # Update only archives that have been marked as "changed". + def update_dirty_archives(self): + for i in self._dirty_archives: + self.update_archive(i) + self._dirty_archives = [] + + # Read a Unix mailbox file from the file object , + # and create a series of Article objects. Each article + # object will then be archived. + + def _makeArticle(self, msg, sequence): + return Article(msg, sequence) + + def processUnixMailbox(self, input, start=None, end=None): + mbox = ArchiverMailbox(input, self.maillist) + if start is None: + start = 0 + counter = 0 + while counter < start: + try: + m = mbox.next() + except Errors.DiscardMessage: + continue + if m is None: + return + counter += 1 + while 1: + try: + pos = input.tell() + m = mbox.next() + except Errors.DiscardMessage: + continue + except Exception: + syslog('error', 'uncaught archiver exception at filepos: %s', + pos) + raise + if m is None: + break + if m == '': + # It was an unparseable message + continue + msgid = m.get('message-id', 'n/a') + self.message(_('#%(counter)05d %(msgid)s')) + a = self._makeArticle(m, self.sequence) + self.sequence += 1 + self.add_article(a) + if end is not None and counter >= end: + break + counter += 1 + + def new_archive(self, archive, archivedir): + self.archives.append(archive) + self.update_TOC = 1 + self.database.newArchive(archive) + # If the archive directory doesn't exist, create it + try: + os.stat(archivedir) + except os.error, errdata: + errno, errmsg = errdata + if errno == 2: + omask = os.umask(0) + try: + os.mkdir(archivedir, self.DIRMODE) + finally: + os.umask(omask) + else: + raise os.error, errdata + self.open_new_archive(archive, archivedir) + + def add_article(self, article): + archives = self.get_archives(article) + if not archives: + return + if type(archives) == type(''): + archives = [archives] + + article.filename = filename = self.get_filename(article) + temp = self.format_article(article) + for arch in archives: + self.archive = arch # why do this??? + archivedir = os.path.join(self.basedir, arch) + if arch not in self.archives: + self.new_archive(arch, archivedir) + + # Write the HTML-ized article + self.write_article(arch, temp, os.path.join(archivedir, + filename)) + + if article.decoded.has_key('author'): + author = fixAuthor(article.decoded['author']) + else: + author = fixAuthor(article.author) + if article.decoded.has_key('stripped'): + subject = article.decoded['stripped'].lower() + else: + subject = article.subject.lower() + + article.parentID = parentID = self.get_parent_info(arch, article) + if parentID: + parent = self.database.getArticle(arch, parentID) + article.threadKey = parent.threadKey + article.date + '-' + else: + article.threadKey = article.date + '-' + key = article.threadKey, article.msgid + + self.database.setThreadKey(arch, key, article.msgid) + self.database.addArticle(arch, temp, author=author, + subject=subject) + + if arch not in self._dirty_archives: + self._dirty_archives.append(arch) + + def get_parent_info(self, archive, article): + parentID = None + if article.in_reply_to: + parentID = article.in_reply_to + elif article.references: + refs = self._remove_external_references(article.references) + if refs: + maxdate = self.database.getArticle(archive, refs[0]) + for ref in refs[1:]: + a = self.database.getArticle(archive, ref) + if a.date > maxdate.date: + maxdate = a + parentID = maxdate.msgid + else: + # Get the oldest article with a matching subject, and + # assume this is a follow-up to that article + parentID = self.database.getOldestArticle(archive, + article.subject) + + if parentID and not self.database.hasArticle(archive, parentID): + parentID = None + return parentID + + def write_article(self, index, article, path): + omask = os.umask(002) + try: + f = open(path, 'w') + finally: + os.umask(omask) + temp_stdout, sys.stdout = sys.stdout, f + self.write_article_header(article) + sys.stdout.writelines(article.body) + self.write_article_footer(article) + sys.stdout = temp_stdout + f.close() + + def _remove_external_references(self, refs): + keep = [] + for ref in refs: + if self.database.hasArticle(self.archive, ref): + keep.append(ref) + return keep + + # Abstract methods: these will need to be overridden by subclasses + # before anything useful can be done. + + def get_filename(self, article): + pass + def get_archives(self, article): + """Return a list of indexes where the article should be filed. + A string can be returned if the list only contains one entry, + and the empty list is legal.""" + pass + def format_article(self, article): + pass + def write_index_header(self): + pass + def write_index_footer(self): + pass + def write_index_entry(self, article): + pass + def write_threadindex_entry(self, article, depth): + pass + def write_article_header(self, article): + pass + def write_article_footer(self, article): + pass + def write_article_entry(self, article): + pass + def update_article(self, archivedir, article, prev, next): + pass + def write_TOC(self): + pass + def open_new_archive(self, archive, dir): + pass + def message(self, msg): + pass + + +class BSDDBdatabase(Database): + __super_addArticle = Database.addArticle + + def __init__(self, basedir): + self.__cachekeys = [] + self.__cachedict = {} + self.__currentOpenArchive = None # The currently open indices + self.basedir = os.path.expanduser(basedir) + self.changed = {} # Recently added articles, indexed only by + # message ID + + def firstdate(self, archive): + self.__openIndices(archive) + date = 'None' + try: + date, msgid = self.dateIndex.first() + date = time.asctime(time.localtime(float(date))) + except KeyError: + pass + return date + + def lastdate(self, archive): + self.__openIndices(archive) + date = 'None' + try: + date, msgid = self.dateIndex.last() + date = time.asctime(time.localtime(float(date))) + except KeyError: + pass + return date + + def numArticles(self, archive): + self.__openIndices(archive) + return len(self.dateIndex) + + def addArticle(self, archive, article, subject=None, author=None, + date=None): + self.__openIndices(archive) + self.__super_addArticle(archive, article, subject, author, date) + + # Open the BSDDB files that are being used as indices + # (dateIndex, authorIndex, subjectIndex, articleIndex) + def __openIndices(self, archive): + if self.__currentOpenArchive == archive: + return + + import bsddb + self.__closeIndices() + arcdir = os.path.join(self.basedir, 'database') + omask = os.umask(0) + try: + try: + os.mkdir(arcdir, 02775) + except OSError: + # BAW: Hmm... + pass + finally: + os.umask(omask) + for hdr in ('date', 'author', 'subject', 'article', 'thread'): + path = os.path.join(arcdir, archive + '-' + hdr) + t = bsddb.btopen(path, 'c') + setattr(self, hdr + 'Index', t) + self.__currentOpenArchive = archive + + # Close the BSDDB files that are being used as indices (if they're + # open--this is safe to call if they're already closed) + def __closeIndices(self): + if self.__currentOpenArchive is not None: + pass + for hdr in ('date', 'author', 'subject', 'thread', 'article'): + attr = hdr + 'Index' + if hasattr(self, attr): + index = getattr(self, attr) + if hdr == 'article': + if not hasattr(self, 'archive_length'): + self.archive_length = {} + self.archive_length[self.__currentOpenArchive] = len(index) + index.close() + delattr(self,attr) + self.__currentOpenArchive = None + + def close(self): + self.__closeIndices() + def hasArticle(self, archive, msgid): + self.__openIndices(archive) + return self.articleIndex.has_key(msgid) + def setThreadKey(self, archive, key, msgid): + self.__openIndices(archive) + self.threadIndex[key] = msgid + def getArticle(self, archive, msgid): + self.__openIndices(archive) + if self.__cachedict.has_key(msgid): + self.__cachekeys.remove(msgid) + self.__cachekeys.append(msgid) + return self.__cachedict[msgid] + if len(self.__cachekeys) == CACHESIZE: + delkey, self.__cachekeys = (self.__cachekeys[0], + self.__cachekeys[1:]) + del self.__cachedict[delkey] + s = self.articleIndex[msgid] + article = pickle.loads(s) + self.__cachekeys.append(msgid) + self.__cachedict[msgid] = article + return article + + def first(self, archive, index): + self.__openIndices(archive) + index = getattr(self, index+'Index') + try: + key, msgid = index.first() + return msgid + except KeyError: + return None + def next(self, archive, index): + self.__openIndices(archive) + index = getattr(self, index+'Index') + try: + key, msgid = index.next() + except KeyError: + return None + else: + return msgid + + def getOldestArticle(self, archive, subject): + self.__openIndices(archive) + subject = subject.lower() + try: + key, tempid = self.subjectIndex.set_location(subject) + self.subjectIndex.next() + [subject2, date] = key.split('\0') + if subject != subject2: + return None + return tempid + except KeyError: # XXX what line raises the KeyError? + return None + + def newArchive(self, archive): + pass + + def clearIndex(self, archive, index): + self.__openIndices(archive) + index = getattr(self, index+'Index') + finished = 0 + try: + key, msgid = self.threadIndex.first() + except KeyError: + finished = 1 + while not finished: + del self.threadIndex[key] + try: + key, msgid = self.threadIndex.next() + except KeyError: + finished = 1 + + diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/Auth.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/Auth.py new file mode 100755 index 0000000..9e9f908 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/Auth.py @@ -0,0 +1,72 @@ +# Copyright (C) 1998,1999,2000,2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Common routines for logging in and logging out of the list administrator +and list moderator interface. +""" + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Errors +from Mailman.htmlformat import Link +from Mailman.htmlformat import HTMLFormatObject + +from Mailman.i18n import _ + + + +class NotLoggedInError(Exception): + """Exception raised when no matching admin cookie was found.""" + def __init__(self, message): + Exception.__init__(self, message) + self.message = message + + + +def loginpage(mlist, scriptname, msg='', frontpage=None): + url = mlist.GetScriptURL(scriptname) + if frontpage: + actionurl = url + else: + actionurl = Utils.GetRequestURI(url) + # if msg: + # The format of msg comes from where it was originated. This way + # the msg can come with more than one design (ej. success or + # error). This is just a consideration because it seems that + # here the msg's value always be error design (failed logins in + # this case). + # msg = ... + if scriptname == 'admindb': + who = _('Moderator') + else: + who = _('Administrator') + # Language stuff + charset = Utils.GetCharSet(mlist.preferred_language) + print 'Content-type: text/html; charset=' + charset + '\n\n' + print Utils.maketext( + 'admlogin.html', + {'listname': mlist.real_name, + 'path' : actionurl, + 'message' : msg, + 'who' : who, + # Links on header section (errormsg) + 'listadmin_link': Link(Utils.ScriptURL('admin'), _('Administration')).Format(), + 'listinfo_link': Link(Utils.ScriptURL('listinfo'), _('General Information')).Format(), + 'errormsg_header': _('Mailing Lists'), + }, mlist=mlist) + print mlist.GetMailmanFooter() + # We need to close some tags at this point + print '' + '\n' + '' diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/__init__.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/__init__.py new file mode 100755 index 0000000..f569e43 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 1998,1999,2000,2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/admin.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/admin.py new file mode 100755 index 0000000..1243250 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/admin.py @@ -0,0 +1,1482 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Process and produce the list-administration options forms.""" + +# For Python 2.1.x compatibility +from __future__ import nested_scopes + +import sys +import os +import re +import cgi +import sha +import urllib +import signal +from types import * +from string import lowercase, digits + +from email.Utils import unquote, parseaddr, formataddr + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman import MemberAdaptor +from Mailman import i18n +from Mailman.UserDesc import UserDesc +from Mailman.htmlformat import * +from Mailman.Cgi import Auth +from Mailman.Logging.Syslog import syslog + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + +NL = '\n' +OPTCOLUMNS = 11 + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +def main(): + # Try to find out which list is being administered + parts = Utils.GetPathPieces() + if not parts: + # None, so just do the admin overview and be done with it + admin_overview() + return + # Get the list object + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + admin_overview(_('No such list %(safelistname)s')) + syslog('error', 'admin.py access for non-existent list: %s', + listname) + return + # Now that we know what list has been requested, all subsequent admin + # pages are shown in that list's preferred language. + i18n.set_language(mlist.preferred_language) + # If the user is not authenticated, we're done. + cgidata = cgi.FieldStorage(keep_blank_values=1) + + if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + cgidata.getvalue('adminpw', '')): + if cgidata.has_key('adminpw'): + # This is a re-authorization attempt + msg = Div(Paragraph(_('Authorization failed.'))).Format(css='class="message error strong"') + else: + msg = '' + Auth.loginpage(mlist, 'admin', msg=msg) + return + + # Which subcategory was requested? Default is `general' + if len(parts) == 1: + category = 'general' + subcat = None + elif len(parts) == 2: + category = parts[1] + subcat = None + else: + category = parts[1] + subcat = parts[2] + + # Is this a log-out request? + if category == 'logout': + print mlist.ZapCookie(mm_cfg.AuthListAdmin) + Auth.loginpage(mlist, 'admin', frontpage=1) + return + + # Sanity check + if category not in mlist.GetConfigCategories().keys(): + category = 'general' + + # Is the request for variable details? + varhelp = None + qsenviron = os.environ.get('QUERY_STRING') + parsedqs = None + if qsenviron: + parsedqs = cgi.parse_qs(qsenviron) + if cgidata.has_key('VARHELP'): + varhelp = cgidata.getvalue('VARHELP') + elif parsedqs: + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = parsedqs.get('VARHELP') + if qs and isinstance(qs, ListType): + varhelp = qs[0] + if varhelp: + option_help(mlist, varhelp) + return + + # The html page document + doc = Document() + doc.set_language(mlist.preferred_language) + + # From this point on, the MailList object must be locked. However, we + # must release the lock no matter how we exit. try/finally isn't enough, + # because of this scenario: user hits the admin page which may take a long + # time to render; user gets bored and hits the browser's STOP button; + # browser shuts down socket; server tries to write to broken socket and + # gets a SIGPIPE. Under Apache 1.3/mod_cgi, Apache catches this SIGPIPE + # (I presume it is buffering output from the cgi script), then turns + # around and SIGTERMs the cgi process. Apache waits three seconds and + # then SIGKILLs the cgi process. We /must/ catch the SIGTERM and do the + # most reasonable thing we can in as short a time period as possible. If + # we get the SIGKILL we're screwed (because it's uncatchable and we'll + # have no opportunity to clean up after ourselves). + # + # This signal handler catches the SIGTERM, unlocks the list, and then + # exits the process. The effect of this is that the changes made to the + # MailList object will be aborted, which seems like the only sensible + # semantics. + # + # BAW: This may not be portable to other web servers or cgi execution + # models. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + + if cgidata.keys(): + # There are options to change + change_options(mlist, category, subcat, cgidata, doc) + # Let the list sanity check the changed values + mlist.CheckValues() + # Additional sanity checks + if not mlist.digestable and not mlist.nondigestable: + doc.addError( + _('''You have turned off delivery of both digest and non-digest messages. This is an incompatible state of affairs. You must turn on either digest delivery or non-digest delivery or your mailing list will basically be unusable.'''), tag=_('Warning: ')) + + if not mlist.digestable and mlist.getDigestMemberKeys(): + doc.addError( + _('''You have digest members, but digests are turned off. Those people will not receive mail.'''), + tag=_('Warning: ')) + if not mlist.nondigestable and mlist.getRegularMemberKeys(): + doc.addError( + _('''You have regular list members but non-digestified mail is turned off. They will receive mail until you fix this problem.'''), tag=_('Warning: ')) + # Glom up the results page and print it out + show_results(mlist, doc, category, subcat, cgidata) + print doc.Format() + mlist.Save() + finally: + # Now be sure to unlock the list. It's okay if we get a signal here + # because essentially, the signal handler will do the same thing. And + # unlocking is unconditional, so it's not an error if we unlock while + # we're already unlocked. + mlist.Unlock() + + + +def admin_overview(msg=''): + # Show the administrative overview page, with the list of all the lists on + # this host. msg is an optional error message to display at the top of + # the page. + # + # This page should be displayed in the server's default language, which + # should have already been set. + hostname = Utils.get_domain() + title = _('Administration') + ' - ' + _('Mailing Lists') + # The html `document' + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.SetTitle(title) + # The table that will hold everything + table = Table() + # Skip any mailing list that isn't advertised. + advertised = [] + listnames = Utils.list_names() + listnames.sort() + + for name in listnames: + mlist = MailList.MailList(name, lock=0) + if mlist.advertised: + if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ + mlist.web_page_url.find(hostname) == -1: + # List is for different identity of this host - skip it. + continue + else: + advertised.append((mlist.GetScriptURL('admin'), + mlist.real_name, + mlist.description)) + # Greeting depends on whether there was an error or not + #if msg: + # greeting = FontAttr(msg, color="ff5060", size="+1") + #else: + # greeting = _("Welcome!") + + welcome = Header(1, _('Administration')).Format() + mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() + if not advertised: + welcome += Paragraph( + _('''There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.''')).Format(css='class="strong"') + else: + welcome += Paragraph( + _('''Below is the collection of publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s. Click on a list name to visit the configuration pages for that list.''')).Format() + + creatorurl = Utils.ScriptURL('create') + mailman_owner = Utils.get_site_email() + extra = msg and _('right ') or '' + welcome += Paragraph( + _('''To visit the administrators configuration page for an unadvertised list, open a URL similar to this one, but with a '/' and the %(extra)slist name appended. If you have the proper authority, you can also create a new mailing list.''')).Format() + + welcome += Paragraph( + _('''General list information can be found at ''') + + Link(Utils.ScriptURL('listinfo'), + _('the mailing list overview page')).Format() + '.').Format() + + welcome += Paragraph(_('(Send questions and comments to ') + + Link('mailto:%s' % mailman_owner, mailman_owner).Format() + + '.)').Format() + + if advertised: + highlight = 1 + for url, real_name, description in advertised: + table.AddRow( + [Link(url, real_name), + description or _('[no description available]')]) + + if highlight: + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="title strong"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="title left"') + else: + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="strong"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + highlight = not highlight + + doc.AddItem(welcome) + # When printing the mailing list table; avoid empty tag to appear when + # no mailing list / rows are present inside it. Empty
      tags are a violation + # in the "-//W3C//DTD XHTML 1.0 Transitional//EN" standard. + if advertised: + doc.AddItem(table) + doc.AddItem(MailmanLogo()) + print doc.Format() + + + +def option_help(mlist, varhelp): + # The html page document + doc = Document() + doc.set_language(mlist.preferred_language) + # Find out which category and variable help is being requested for. + item = None + reflist = varhelp.split('/') + if len(reflist) >= 2: + category = subcat = None + if len(reflist) == 2: + category, varname = reflist + elif len(reflist) == 3: + category, subcat, varname = reflist + options = mlist.GetConfigInfo(category, subcat) + for i in options: + if i and i[0] == varname: + item = i + break + # Print an error message if we couldn't find a valid one + if not item: + bad = _('No valid variable name found.') + doc.addError(bad) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + return + # Get the details about the variable + varname, kind, params, dependancies, description, elaboration = \ + get_item_characteristics(item) + # Set up the document + realname = mlist.real_name + doc.SetTitle(_("Mailman %(varname)s List Option Help")) + doc.AddItem(Header(1, _('%(realname)s Mailing list Configuration Help')).Format()) + doc.AddItem(Header(2, _('%(varname)s Option')).Format()) + doc.AddItem(Paragraph(_("%s (%s): %s" % (varname, category, description))).Format()) + + if elaboration: + doc.AddItem(Paragraph("%s" % elaboration).Format()) + + if subcat: + url = '%s/%s/%s' % (mlist.GetScriptURL('admin'), category, subcat) + else: + url = '%s/%s' % (mlist.GetScriptURL('admin'), category) + + form = Form(url) + valtab = Table() + add_options_table_item(mlist, category, subcat, valtab, item, detailsp=0) + form.AddItem(valtab) + form.AddItem(submit_button()) + doc.AddItem(form) + + adminurl = mlist.GetScriptURL('admin') + + if subcat: + url = '%s/%s/%s' % (adminurl, category, subcat) + else: + url = '%s/%s' % (adminurl, category) + + categoryname = mlist.GetConfigCategories()[category][0] + doc.AddItem(Paragraph( + _('''Warning: changing this option here could cause other screens to be out-of-sync. Be sure to reload any other pages that are displaying this option for this mailing list. You can also ''') + + Link(url, _('return to the %(categoryname)s options page')).Format() + + '.')) + + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + + + +def show_results(mlist, doc, category, subcat, cgidata): + # Produce the results page + adminurl = mlist.GetScriptURL('admin') + categories = mlist.GetConfigCategories() + label = _(categories[category][0]) + + # Set up the document's headers + realname = mlist.real_name + doc.SetTitle(_('%(realname)s Administration (%(label)s)')) + doc.AddItem(Header(1, _('%(realname)s mailing list administration')).Format()) + # Now we need to craft the form that will be submitted, which will contain + # all the variable settings, etc. This is a bit of a kludge because we + # know that the autoreply and members categories supports file uploads. + encoding = None + if category in ('autoreply', 'members'): + encoding = 'multipart/form-data' + if subcat: + form = Form('%s/%s/%s' % (adminurl, category, subcat), + encoding=encoding) + else: + form = Form('%s/%s' % (adminurl, category), encoding=encoding) + # The `other links' are stuff in the right column. + otherlinks = UnorderedList() + otherlinks.AddItem(Link(mlist.GetScriptURL('admindb'), + _('Pending moderator requests'))) + otherlinks.AddItem(Link(mlist.GetScriptURL('listinfo'), + _('General list information'))) + otherlinks.AddItem(Link(mlist.GetScriptURL('edithtml'), + _('Public Templates'))) + otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(), + _('List archives')).Format()) + # We do not allow through-the-web deletion of the site list! + if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and \ + mlist.internal_name() <> mm_cfg.MAILMAN_SITE_LIST: + otherlinks.AddItem(Link(mlist.GetScriptURL('rmlist'), + _('Delete this mailing list')).Format() + + _(' (requires confirmation)')) + otherlinks.AddItem(Link('%s/logout' % adminurl, + # BAW: What I really want is a blank line, but + # adding an   won't do it because of the + # bullet added to the list item. + '%s' % _('Logout'))) + # These are links to other categories and live in the left column + categorylinks_1 = categorylinks = UnorderedList() + categorylinks_2 = '' + categorykeys = categories.keys() + half = len(categorykeys) / 2 + counter = 0 + subcat = None + for k in categorykeys: + label = _(categories[k][0]) + url = '%s/%s' % (adminurl, k) + if k == category: + # Handle subcategories + subcats = mlist.GetConfigSubCategories(k) + if subcats: + subcat = Utils.GetPathPieces()[-1] + for k, v in subcats: + if k == subcat: + break + else: + # The first subcategory in the list is the default + subcat = subcats[0][0] + subcat_items = [] + for sub, text in subcats: + if sub == subcat: + text = Bold('[%s]' % text).Format() + subcat_items.append(Link(url + '/' + sub, text)) + categorylinks.AddItem( + Bold(label).Format() + + UnorderedList(*subcat_items).Format()) + else: + categorylinks.AddItem(Link(url, Bold('[%s]' % label))) + else: + categorylinks.AddItem(Link(url, label)) + counter += 1 + if counter >= half: + categorylinks_2 = categorylinks = UnorderedList() + counter = -len(categorykeys) + # ...and add the links to the document. + form.AddItem('
      ') + form.AddItem('

      ' + _('Other options') + '

      ') + form.AddItem(otherlinks.Format(css='class="adminpanel"')) + form.AddItem('
      ') + form.AddItem('
      ') + form.AddItem('

      ' + _('Configuration options') + '

      ') + form.AddItem(categorylinks_1.Format(css='class="adminpanel floatl"')) + form.AddItem(categorylinks_2.Format(css='class="adminpanel floatl"')) + form.AddItem('
      ') + # Make the emergency stop switch a rude solo light + if mlist.emergency: + label = _('Emergency moderation of all list traffic is enabled') + form.AddItem(Paragraph(Link('?VARHELP=general/emergency', label)).Format(css='class="emergency"')) + form.AddItem('
      ') + form.AddItem(Paragraph(_('''Make your changes in the following section, then submit them using the Submit Your Changes button below.''')).Format()) + + # The members and passwords categories are special in that they aren't + # defined in terms of gui elements. Create those pages here. + if category == 'members': + + # Figure out which subcategory we should display + subcat = Utils.GetPathPieces()[-1] + if subcat not in ('list', 'add', 'remove'): + subcat = 'list' + + # Add member category specific tables + form.AddItem(membership_options(mlist, subcat, cgidata, doc, form)) + form.AddItem(submit_button('setmemberopts_btn')) + + # In "list" subcategory, we can also search for members + if subcat == 'list': + table = Table() + container = Container() + container.AddItem(Header(2, _('Additional Member Tasks')).Format()) + + # Add a section to set the moderation bit for all members + table.AddRow([ + _("""Set everyone's moderation bit, including those members not currently visible:"""), + RadioButtonArray('allmodbit_val', + (_('Off'), _('On')), + mlist.default_member_moderation)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + table.AddRow([SubmitButton('allmodbit_btn', _('Set'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') + + form.AddItem(container) + form.AddItem(table) + + elif category == 'passwords': + form.AddItem(password_inputs(mlist)) + form.AddItem(submit_button()) + else: + form.AddItem(show_variables(mlist, category, subcat, cgidata, doc)) + form.AddItem(submit_button()) + # And add the form + doc.AddItem(form) + doc.AddItem(mlist.GetMailmanFooter()) + + + +def show_variables(mlist, category, subcat, cgidata, doc): + options = mlist.GetConfigInfo(category, subcat) + + # The table containing the results + table = Table() + + # Get and portray the text label for the category. + categories = mlist.GetConfigCategories() + label = _(categories[category][0]) + + container = Container() + container.AddItem(Header(2, label).Format()) + + # The very first item in the config info will be treated as a general + # description if it is a string + description = options[0] + if isinstance(description, StringType): + container.AddItem(description) + options = options[1:] + + if not options: + return container + + # Add the global column headers + table.AddRow([_('Description:'), + _('Value')]) + table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, css='class="description center strong"') + table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 1, css='class="value center strong"') + + for item in options: + if type(item) == StringType: + # The very first banner option (string in an options list) is + # treated as a general description, while any others are + # treated as section headers - centered and italicized... + table.AddRow([Header(3, item).Format(css='class="center"')]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + else: + add_options_table_item(mlist, category, subcat, table, item) + + container.AddItem(table) + return container + + + +def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): + # Add a row to an options table with the item description and value. + varname, kind, params, extra, descr, elaboration = \ + get_item_characteristics(item) + if elaboration is None: + elaboration = descr + descr = get_item_gui_description(mlist, category, subcat, + varname, descr, elaboration, detailsp) + val = get_item_gui_value(mlist, category, kind, varname, params, extra) + table.AddRow([descr, val]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + + +def get_item_characteristics(record): + # Break out the components of an item description from its description + # record: + # + # 0 -- option-var name + # 1 -- type + # 2 -- entry size + # 3 -- ?dependancies? + # 4 -- Brief description + # 5 -- Optional description elaboration + if len(record) == 5: + elaboration = None + varname, kind, params, dependancies, descr = record + elif len(record) == 6: + varname, kind, params, dependancies, descr, elaboration = record + else: + raise ValueError, _('Badly formed options entry: %(record)s') + return varname, kind, params, dependancies, descr, elaboration + + + +def get_item_gui_value(mlist, category, kind, varname, params, extra): + """Return a representation of an item's settings.""" + # Give the category a chance to return the value for the variable + value = None + label, gui = mlist.GetConfigCategories()[category] + if hasattr(gui, 'getValue'): + value = gui.getValue(mlist, kind, varname, params) + # Filter out None, and volatile attributes + if value is None and not varname.startswith('_'): + value = getattr(mlist, varname) + # Now create the widget for this value + if kind == mm_cfg.Radio or kind == mm_cfg.Toggle: + # If we are returning the option for subscribe policy and this site + # doesn't allow open subscribes, then we have to alter the value of + # mlist.subscribe_policy as passed to RadioButtonArray in order to + # compensate for the fact that there is one fewer option. + # Correspondingly, we alter the value back in the change options + # function -scott + # + # TBD: this is an ugly ugly hack. + if varname.startswith('_'): + checked = 0 + else: + checked = value + if varname == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE: + checked = checked - 1 + # For Radio buttons, we're going to interpret the extra stuff as a + # horizontal/vertical flag. For backwards compatibility, the value 0 + # means horizontal, so we use "not extra" to get the parity right. + return RadioButtonArray(varname, params, checked, not extra) + elif (kind == mm_cfg.String or kind == mm_cfg.Email or + kind == mm_cfg.Host or kind == mm_cfg.Number): + return TextBox(varname, value, params) + elif kind == mm_cfg.Text: + if params: + r, c = params + else: + r, c = None, None + return TextArea(varname, value or '', r, c) + elif kind in (mm_cfg.EmailList, mm_cfg.EmailListEx): + if params: + r, c = params + else: + r, c = None, None + res = NL.join(value) + return TextArea(varname, res, r, c) + elif kind == mm_cfg.FileUpload: + # like a text area, but also with uploading + if params: + r, c = params + else: + r, c = None, None + container = Container() + container.AddItem('' + _('Enter the text below') + ':
      ') + container.AddItem(TextArea(varname, value or '', r, c)) + container.AddItem('
      ' + _('... or specify a file to upload') + ':
      ') + container.AddItem(FileUpload(varname+'_upload', r, c)) + return container + elif kind == mm_cfg.Select: + if params: + values, legend, selected = params + else: + values = mlist.GetAvailableLanguages() + legend = map(_, map(Utils.GetLanguageDescr, values)) + selected = values.index(mlist.preferred_language) + return SelectOptions(varname, values, legend, selected) + elif kind == mm_cfg.Topics: + # A complex and specialized widget type that allows for setting of a + # topic name, a mark button, a regexp text box, an "add after mark", + # and a delete button. Yeesh! params are ignored. + table = Table() + # This adds the html for the entry widget + def makebox(i, name, pattern, desc, empty=False, table=table): + deltag = 'topic_delete_%02d' % i + boxtag = 'topic_box_%02d' % i + reboxtag = 'topic_rebox_%02d' % i + desctag = 'topic_desc_%02d' % i + wheretag = 'topic_where_%02d' % i + addtag = 'topic_add_%02d' % i + newtag = 'topic_new_%02d' % i + if empty: + table.AddRow([Paragraph(_('Topic %(i)d')).Format(), + Paragraph(Hidden(newtag)).Format()]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="header strong center"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="header"') + else: + table.AddRow([Paragraph(_('Topic %(i)d')).Format(), + Paragraph(SubmitButton(deltag, _('Delete'))).Format()]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="header strong center"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="header"') + + table.AddRow([_('Topic name:'), + TextBox(boxtag, value=name, size=30)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + + table.AddRow([_('Regexp:'), + TextArea(reboxtag, text=pattern, + rows=4, cols=30)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + + table.AddRow([_('Description:'), + TextArea(desctag, text=desc, + rows=4, cols=30)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + + if not empty: + table.AddRow([SubmitButton(addtag, _('Add new item...')), + SelectOptions(wheretag, ('before', 'after'), + (_('...before this one.'), + _('...after this one.')), + selected=1), + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + # Now for each element in the existing data, create a widget + i = 1 + data = getattr(mlist, varname) + for name, pattern, desc, empty in data: + makebox(i, name, pattern, desc, empty) + i += 1 + # Add one more non-deleteable widget as the first blank entry, but + # only if there are no real entries. + if i == 1: + makebox(i, '', '', '', empty=True) + return table + elif kind == mm_cfg.HeaderFilter: + # A complex and specialized widget type that allows for setting of a + # spam filter rule including, a mark button, a regexp text box, an + # "add after mark", up and down buttons, and a delete button. Yeesh! + # params are ignored. + table = Table(border=0) + # This adds the html for the entry widget + def makebox(i, pattern, action, empty=False, table=table): + deltag = 'hdrfilter_delete_%02d' % i + reboxtag = 'hdrfilter_rebox_%02d' % i + actiontag = 'hdrfilter_action_%02d' % i + wheretag = 'hdrfilter_where_%02d' % i + addtag = 'hdrfilter_add_%02d' % i + newtag = 'hdrfilter_new_%02d' % i + uptag = 'hdrfilter_up_%02d' % i + downtag = 'hdrfilter_down_%02d' % i + if empty: + table.AddRow([Paragraph(_('Spam Filter Rule %(i)d')).Format(), + Paragraph(Hidden(newtag)).Format()]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="header strong center"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="header"') + else: + table.AddRow([Paragraph(_('Spam Filter Rule %(i)d')).Format(), + Paragraph(SubmitButton(deltag, _('Delete'))).Format()]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="header strong center"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="header"') + + table.AddRow([_('Spam Filter Regexp:'), + TextArea(reboxtag, text=pattern, + rows=4, cols=30)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + + values = [mm_cfg.DEFER, mm_cfg.HOLD, mm_cfg.REJECT, + mm_cfg.DISCARD, mm_cfg.ACCEPT] + try: + checked = values.index(action) + except ValueError: + checked = 0 + radio = RadioButtonArray( + actiontag, + (_('Defer'), _('Hold'), _('Reject'), + _('Discard'), _('Accept')), + values=values, + checked=checked).Format() + table.AddRow([_('Action:'), radio]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center"') + if not empty: + table.AddRow([SubmitButton(addtag, _('Add new item...')), + SelectOptions(wheretag, ('before', 'after'), + (_('...before this one.'), + _('...after this one.')), + selected=1) + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + # BAW: IWBNI we could disable the up and down buttons for the + # first and last item respectively, but it's not easy to know + # which is the last item, so let's not worry about that for + # now. + table.AddRow([SubmitButton(uptag, _('Move rule up')), + SubmitButton(downtag, _('Move rule down'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="right"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + # Now for each element in the existing data, create a widget + i = 1 + data = getattr(mlist, varname) + for pattern, action, empty in data: + makebox(i, pattern, action, empty) + i += 1 + # Add one more non-deleteable widget as the first blank entry, but + # only if there are no real entries. + if i == 1: + makebox(i, '', mm_cfg.DEFER, empty=True) + return table + elif kind == mm_cfg.Checkbox: + return CheckBoxArray(varname, *params) + else: + assert 0, 'Bad gui widget type: %s' % kind + + + +def get_item_gui_description(mlist, category, subcat, + varname, descr, elaboration, detailsp): + # Return the item's description, with link to details. + # + # Details are not included if this is a VARHELP page, because that /is/ + # the details page! + if detailsp: + if subcat: + varhelp = '/?VARHELP=%s/%s/%s' % (category, subcat, varname) + else: + varhelp = '/?VARHELP=%s/%s' % (category, varname) + if descr == elaboration: + linktext = _('(Edit %(varname)s)') + else: + linktext = _('(Details for %(varname)s)') + link = Link(mlist.GetScriptURL('admin') + varhelp, + linktext).Format() + text = '%s
      %s' % (descr, link) + else: + text = descr + if varname[0] == '_': + text += Paragraph(_('''Note: setting this value performs an immediate action but does not modify permanent state.''')).Format() + return text + + + +def membership_options(mlist, subcat, cgidata, doc, form): + # Show the main stuff + adminurl = mlist.GetScriptURL('admin', absolute=1) + container = Container() + # If we're in the list subcategory, show the membership list + if subcat == 'add': + container.AddItem(Header(2, _('Mass Subscriptions')).Format()) + mass_subscribe(mlist, container) + return container + if subcat == 'remove': + container.AddItem(Header(2, _('Mass Removals')).Format()) + mass_remove(mlist, container) + return container + # Otherwise... + container.AddItem(Header(2, _('Membership List')).Format()) + # Add a "search for member" button + link = Link('http://www.python.org/doc/current/lib/re-syntax.html', + _('(help)')).Format() + container.AddItem(Paragraph( + _('Find member %(link)s:') + + TextBox('findmember', value=cgidata.getvalue('findmember', '')).Format() + + SubmitButton('findmember_btn', _('Search...')).Format()).Format()) + + usertable = Table(css='class="mm_usertable"') + # If there are more members than allowed by chunksize, then we split the + # membership up alphabetically. Otherwise just display them all. + chunksz = mlist.admin_member_chunksize + # The email addresses had /better/ be ASCII, but might be encoded in the + # database as Unicodes. + all = [_m.encode() for _m in mlist.getMembers()] + all.sort(lambda x, y: cmp(x.lower(), y.lower())) + # See if the query has a regular expression + regexp = cgidata.getvalue('findmember', '').strip() + if regexp: + try: + cre = re.compile(regexp, re.IGNORECASE) + except re.error: + doc.addError(_('Bad regular expression: ') + regexp) + else: + # BAW: There's got to be a more efficient way of doing this! + names = [mlist.getMemberName(s) or '' for s in all] + all = [a for n, a in zip(names, all) + if cre.search(n) or cre.search(a)] + chunkindex = None + bucket = None + actionurl = None + if len(all) < chunksz: + members = all + else: + # Split them up alphabetically, and then split the alphabetical + # listing by chunks + buckets = {} + for addr in all: + members = buckets.setdefault(addr[0].lower(), []) + members.append(addr) + # Now figure out which bucket we want + bucket = None + qs = {} + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qsenviron = os.environ.get('QUERY_STRING') + if qsenviron: + qs = cgi.parse_qs(qsenviron) + bucket = qs.get('letter', 'a')[0].lower() + if bucket not in digits + lowercase: + bucket = None + if not bucket or not buckets.has_key(bucket): + keys = buckets.keys() + keys.sort() + bucket = keys[0] + members = buckets[bucket] + action = adminurl + '/members?letter=%s' % bucket + if len(members) <= chunksz: + form.set_action(action) + else: + i, r = divmod(len(members), chunksz) + numchunks = i + (not not r * 1) + # Now chunk them up + chunkindex = 0 + if qs.has_key('chunk'): + try: + chunkindex = int(qs['chunk'][0]) + except ValueError: + chunkindex = 0 + if chunkindex < 0 or chunkindex > numchunks: + chunkindex = 0 + members = members[chunkindex*chunksz:(chunkindex+1)*chunksz] + # And set the action URL + form.set_action(action + '&chunk=%s' % chunkindex) + + # So now members holds all the addresses we're going to display + allcnt = len(all) + if bucket: + membercnt = len(members) + usertable.AddRow([Header(3, + _('%(allcnt)s members total, %(membercnt)s shown')).Format()]) + else: + usertable.AddRow([Header(3, + _('%(allcnt)s members total')).Format()]) + usertable.AddCellInfo(usertable.GetCurrentRowIndex(), + usertable.GetCurrentCellIndex(), + colspan=OPTCOLUMNS, + css='class="center"') + + # Add the alphabetical links + if bucket: + cells = [] + for letter in digits + lowercase: + if not buckets.get(letter): + continue + url = adminurl + '/members?letter=%s' % letter + if letter == bucket: + show = Bold('[%s]' % letter.upper()).Format() + else: + show = letter.upper() + cells.append(Link(url, show).Format()) + joiner = ' '*2 + '\n' + usertable.AddRow([joiner.join(cells)]) + usertable.AddCellInfo(usertable.GetCurrentRowIndex(), + usertable.GetCurrentCellIndex(), + colspan=OPTCOLUMNS, + css='class="center"') + + usertable.AddRow([(h) for h in (_('unsub'), + _('member address
      member name'), + _('mod'), _('hide'), + _('nomail
      [reason]'), + _('ack'), _('not metoo'), + _('nodupes'), + _('digest'), _('plain'), + _('language'))]) + rowindex = usertable.GetCurrentRowIndex() + for i in range(OPTCOLUMNS): + usertable.AddCellInfo(rowindex, i, css='class="header strong center"') + + # Find the longest name in the list + longest = 0 + if members: + names = filter(None, [mlist.getMemberName(s) for s in members]) + # Make the name field at least as long as the longest email address + longest = max([len(s) for s in names + members]) + + # Abbreviations for delivery status details + ds_abbrevs = {MemberAdaptor.UNKNOWN : _('?'), + MemberAdaptor.BYUSER : _('U'), + MemberAdaptor.BYADMIN : _('A'), + MemberAdaptor.BYBOUNCE: _('B'), + } + + # Now populate the rows + highlight = 1 + for addr in members: + link = Link(mlist.GetOptionsURL(addr, obscure=1), + mlist.getMemberCPAddress(addr)) + fullname = Utils.uncanonstr(mlist.getMemberName(addr), + mlist.preferred_language) + name = TextBox(addr + '_realname', fullname, size=longest).Format() + cells = [CheckBox(addr + '_unsub', 'off', 0).Format(), + link.Format() + '
      ' + + name + + Hidden('user', urllib.quote(addr)).Format(), + ] + # Do the `mod' option + if mlist.getMemberOption(addr, mm_cfg.Moderate): + value = 'on' + checked = 1 + else: + value = 'off' + checked = 0 + box = CheckBox('%s_mod' % addr, value, checked) + cells.append(box.Format()) + for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes'): + extra = '' + if opt == 'nomail': + status = mlist.getDeliveryStatus(addr) + if status == MemberAdaptor.ENABLED: + value = 'off' + checked = 0 + else: + value = 'on' + checked = 1 + extra = '[%s]' % ds_abbrevs[status] + elif mlist.getMemberOption(addr, mm_cfg.OPTINFO[opt]): + value = 'on' + checked = 1 + else: + value = 'off' + checked = 0 + box = CheckBox('%s_%s' % (addr, opt), value, checked) + cells.append(box.Format() + extra) + + # This code is less efficient than the original which did a has_key on + # the underlying dictionary attribute. This version is slower and + # less memory efficient. It points to a new MemberAdaptor interface + # method. + if addr in mlist.getRegularMemberKeys(): + cells.append(CheckBox(addr + '_digest', 'off', 0).Format()) + else: + cells.append(CheckBox(addr + '_digest', 'on', 1).Format()) + if mlist.getMemberOption(addr, mm_cfg.OPTINFO['plain']): + value = 'on' + checked = 1 + else: + value = 'off' + checked = 0 + cells.append(CheckBox('%s_plain' % addr, value, checked)) + + # User's preferred language + langpref = mlist.getMemberLanguage(addr) + langs = mlist.GetAvailableLanguages() + langdescs = [_(Utils.GetLanguageDescr(lang)) for lang in langs] + try: + selected = langs.index(langpref) + except ValueError: + selected = 0 + cells.append(SelectOptions(addr + '_language', langs, + langdescs, selected).Format()) + + # Put each cell into the row and give format to that row + usertable.AddRow(cells) + if highlight: + usertable.AddRowInfo(usertable.GetCurrentRowIndex(), css='class="title center"') + highlight = not highlight + else: + highlight = 1 + usertable.AddRowInfo(usertable.GetCurrentRowIndex(), css='class="center"') + + # Add the usertable and a legend + legend = UnorderedList() + legend.AddItem( + _('unsub -- Click on this to unsubscribe the member.')) + legend.AddItem( + _("""mod -- The user's personal moderation flag. If this is + set, postings from them will be moderated, otherwise they will be + approved.""")) + legend.AddItem( + _("""hide -- Is the member's address concealed on + the list of subscribers?""")) + legend.AddItem( + _('nomail -- Is delivery to the member disabled? If so, an abbreviation will be given describing the reason for the disabled delivery:') + '\n' + + '
        ' + '\n' + + _('''
      • U -- Delivery was disabled by the user via their personal options page.
      • ''') + '\n' + + _('''
      • A -- Delivery was disabled by the list administrators.
      • ''') + '\n' + + _('''
      • B -- Delivery was disabled by the system due to excessive bouncing from the member's address.
      • ''') + '\n' + + _('''
      • ? -- The reason for disabled delivery isn't known. This is the case for all memberships which were disabled in older versions of Mailman.
      • ''') + '\n' + + '
      ' + '\n') + legend.AddItem( + _('''ack -- Does the member get acknowledgements of their + posts?''')) + legend.AddItem( + _('''not metoo -- Does the member want to avoid copies of their + own postings?''')) + legend.AddItem( + _('''nodupes -- Does the member want to avoid duplicates of the + same message?''')) + legend.AddItem( + _('''digest -- Does the member get messages in digests? + (otherwise, individual messages)''')) + legend.AddItem( + _('''plain -- If getting digests, does the member get plain + text digests? (otherwise, MIME)''')) + legend.AddItem(_("language -- Language preferred by the user")) + addlegend = '' + parsedqs = 0 + qsenviron = os.environ.get('QUERY_STRING') + if qsenviron: + qs = cgi.parse_qs(qsenviron).get('legend') + if qs and isinstance(qs, ListType): + qs = qs[0] + if qs == 'yes': + addlegend = 'legend=yes&' + if addlegend: + container.AddItem(legend.Format()) + container.AddItem(Paragraph( + Link(adminurl + '/members/list', + _('Click here to hide the legend for this table.')))) + else: + container.AddItem(Paragraph( + Link(adminurl + '/members/list?legend=yes', + _('Click here to include the legend for this table.')))) + + container.AddItem(usertable) + + # There may be additional chunks + if chunkindex is not None: + buttons = [] + url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) + footer = _('''To view more members, click on the appropriate range listed below:''') + chunkmembers = buckets[bucket] + last = len(chunkmembers) + for i in range(numchunks): + if i == chunkindex: + continue + start = chunkmembers[i*chunksz] + end = chunkmembers[min((i+1)*chunksz, last)-1] + link = Link(url + 'chunk=%d' % i, _('from %(start)s to %(end)s')) + buttons.append(link) + buttons = UnorderedList(*buttons) + container.AddItem(Paragraph(footer + buttons.Format()).Format()) + return container + + + +def mass_subscribe(mlist, container): + # MASS SUBSCRIBE + table = Table() + table.AddRow([ + _('Subscribe these users now or invite them?'), + RadioButtonArray('subscribe_or_invite', + (_('Subscribe'), _('Invite')), + 0, values=(0, 1)) + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow([ + _('Send welcome messages to new subscribees?'), + RadioButtonArray('send_welcome_msg_to_this_batch', + (_('No'), _('Yes')), + mlist.send_welcome_msg, + values=(0, 1)) + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow([ + _('Send notifications of new subscriptions to the list owner?'), + RadioButtonArray('send_notifications_to_list_owner', + (_('No'), _('Yes')), + mlist.admin_notify_mchanges, + values=(0,1)) + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow([ + _('Enter one address per line:'), + TextArea(name='subscribees', + rows=10, cols='70%').Format() + + Paragraph(_('... or specify a file to upload:') + + FileUpload('subscribees_upload', cols='50').Format()).Format() + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + # Invitation text + table.AddRow([_("""Enter additional text to be added to the + top of your invitation or the subscription notification. Include at least + one blank line at the end:"""), + TextArea(name='invitation', rows=10, cols='70%')]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + container.AddItem(table) + + + +def mass_remove(mlist, container): + # MASS UNSUBSCRIBE + table = Table() + table.AddRow([ + _('Send unsubscription acknowledgement to the user?'), + RadioButtonArray('send_unsub_ack_to_this_batch', + (_('No'), _('Yes')), + 0, values=(0, 1)) + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow([ + _('Send notifications to the list owner?'), + RadioButtonArray('send_unsub_notifications_to_list_owner', + (_('No'), _('Yes')), + mlist.admin_notify_mchanges, + values=(0, 1)) + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow([_('Enter one address per line:'), + TextArea(name='unsubscribees', + rows=10, cols='70%').Format() + + Paragraph(_('... or specify a file to upload:') + + FileUpload('unsubscribees_upload', cols='50').Format()).Format() + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + container.AddItem(table) + + + +def password_inputs(mlist): + adminurl = mlist.GetScriptURL('admin', absolute=1) + container = Container() + container.AddItem(Header(2, _('Change list ownership passwords')).Format()) + container.AddItem(Paragraph(_('''The list administrators are the people who have ultimate control over all parameters of this mailing list. They are able to change any list configuration variable available through these administration web pages.'''))) + container.AddItem(Paragraph(_('''The list moderators have more limited permissions; they are not able to change any list configuration variable, but they are allowed to tend to pending administration requests, including approving or rejecting held subscription requests, and disposing of held postings. Of course, the list administrators can also tend to pending requests.'''))) + container.AddItem(Paragraph(_('''In order to split the list ownership duties into administrators and moderators, you must set a separate moderator password in the fields below, and also provide the email addresses of the list moderators in the general options section.'''))) + # Set up the admin password table on the left + atable = Table() + atable.AddRow([_('Enter new administrator password:'), + PasswordBox('newpw', size=20)]) + atable.AddCellInfo(atable.GetCurrentRowIndex(), 0, css='class="description"') + atable.AddCellInfo(atable.GetCurrentRowIndex(), 1, css='class="value"') + atable.AddRow([_('Confirm administrator password:'), + PasswordBox('confirmpw', size=20)]) + atable.AddCellInfo(atable.GetCurrentRowIndex(), 0, css='class="description"') + atable.AddCellInfo(atable.GetCurrentRowIndex(), 1, css='class="value"') + # Set up the moderator password table on the right + mtable = Table() + mtable.AddRow([_('Enter new moderator password:'), + PasswordBox('newmodpw', size=20)]) + mtable.AddCellInfo(mtable.GetCurrentRowIndex(), 0, css='class="description"') + mtable.AddCellInfo(mtable.GetCurrentRowIndex(), 1, css='class="value"') + mtable.AddRow([_('Confirm moderator password:'), + PasswordBox('confirmmodpw', size=20)]) + mtable.AddCellInfo(mtable.GetCurrentRowIndex(), 0, css='class="description"') + mtable.AddCellInfo(mtable.GetCurrentRowIndex(), 1, css='class="value"') + + # Add these tables to the overall password container + container.AddItem(atable) + container.AddItem(mtable) + return container + + + +def submit_button(name='submit'): + container = Container() + container.AddItem(Paragraph(SubmitButton(name, _('Submit Your Changes'))).Format(css='class="mm_submit"')) + return container + + + +def change_options(mlist, category, subcat, cgidata, doc): + def safeint(formvar, defaultval=None): + try: + return int(cgidata.getvalue(formvar)) + except (ValueError, TypeError): + return defaultval + confirmed = 0 + # Handle changes to the list moderator password. Do this before checking + # the new admin password, since the latter will force a reauthentication. + new = cgidata.getvalue('newmodpw', '').strip() + confirm = cgidata.getvalue('confirmmodpw', '').strip() + if new or confirm: + if new == confirm: + mlist.mod_password = sha.new(new).hexdigest() + # No re-authentication necessary because the moderator's + # password doesn't get you into these pages. + else: + doc.addMessage(_('Moderator passwords did not match'), css='class="message error strong"') + # Handle changes to the list administrator password + new = cgidata.getvalue('newpw', '').strip() + confirm = cgidata.getvalue('confirmpw', '').strip() + if new or confirm: + if new == confirm: + mlist.password = sha.new(new).hexdigest() + # Set new cookie + print mlist.MakeCookie(mm_cfg.AuthListAdmin) + else: + doc.addMessage(_('Administrator passwords did not match'), css='class="message error strong"') + # Give the individual gui item a chance to process the form data + categories = mlist.GetConfigCategories() + label, gui = categories[category] + # BAW: We handle the membership page special... for now. + if category <> 'members': + gui.handleForm(mlist, category, subcat, cgidata, doc) + # mass subscription, removal processing for members category + subscribers = '' + subscribers += cgidata.getvalue('subscribees', '') + subscribers += cgidata.getvalue('subscribees_upload', '') + if subscribers: + entries = filter(None, [n.strip() for n in subscribers.splitlines()]) + send_welcome_msg = safeint('send_welcome_msg_to_this_batch', + mlist.send_welcome_msg) + send_admin_notif = safeint('send_notifications_to_list_owner', + mlist.admin_notify_mchanges) + # Default is to subscribe + subscribe_or_invite = safeint('subscribe_or_invite', 0) + invitation = cgidata.getvalue('invitation', '') + digest = mlist.digest_is_default + if not mlist.digestable: + digest = 0 + if not mlist.nondigestable: + digest = 1 + subscribe_errors = [] + subscribe_success = [] + # Now cruise through all the subscribees and do the deed. BAW: we + # should limit the number of "Successfully subscribed" status messages + # we display. Try uploading a file with 10k names -- it takes a while + # to render the status page. + for entry in entries: + safeentry = Utils.websafe(entry) + fullname, address = parseaddr(entry) + # Canonicalize the full name + fullname = Utils.canonstr(fullname, mlist.preferred_language) + userdesc = UserDesc(address, fullname, + Utils.MakeRandomPassword(), + digest, mlist.preferred_language) + try: + if subscribe_or_invite: + if mlist.isMember(address): + raise Errors.MMAlreadyAMember + else: + mlist.InviteNewMember(userdesc, invitation) + else: + mlist.ApprovedAddMember(userdesc, send_welcome_msg, + send_admin_notif, invitation, + whence='admin mass sub') + except Errors.MMAlreadyAMember: + subscribe_errors.append((safeentry, _('Already a member'))) + except Errors.MMBadEmailError: + if userdesc.address == '': + subscribe_errors.append((_('<blank line>'), + _('Bad/Invalid email address'))) + else: + subscribe_errors.append((safeentry, + _('Bad/Invalid email address'))) + except Errors.MMHostileAddress: + subscribe_errors.append( + (safeentry, _('Hostile address (illegal characters)'))) + except Errors.MembershipIsBanned, pattern: + subscribe_errors.append( + (safeentry, _('Banned address (matched %(pattern)s)'))) + else: + member = Utils.uncanonstr(formataddr((fullname, address))) + subscribe_success.append(Utils.websafe(member)) + + if subscribe_success: + if subscribe_or_invite: + msg = _('Successfully invited:') + else: + msg = _('Successfully subscribed:') + doc.AddItem(Div(Header(3, msg).Format() + + UnorderedList(*subscribe_success).Format(css='class="mailaddresses"') + ).Format(css='class="message success"')) + + if subscribe_errors: + if subscribe_or_invite: + msg = _('Error inviting:') + else: + msg = _('Error subscribing:') + items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] + doc.AddItem(Div(Header(3, msg).Format() + + UnorderedList(*items).Format(css='class="mailaddresses"') + ).Format(css='class="message error"')) + + # Unsubscriptions + removals = '' + if cgidata.has_key('unsubscribees'): + removals += cgidata['unsubscribees'].value + if cgidata.has_key('unsubscribees_upload') and \ + cgidata['unsubscribees_upload'].value: + removals += cgidata['unsubscribees_upload'].value + if removals: + names = filter(None, [n.strip() for n in removals.splitlines()]) + send_unsub_notifications = int( + cgidata['send_unsub_notifications_to_list_owner'].value) + userack = int( + cgidata['send_unsub_ack_to_this_batch'].value) + unsubscribe_errors = [] + unsubscribe_success = [] + for addr in names: + try: + mlist.ApprovedDeleteMember( + addr, whence='admin mass unsub', + admin_notif=send_unsub_notifications, + userack=userack) + unsubscribe_success.append(Utils.websafe(addr)) + except Errors.NotAMemberError: + unsubscribe_errors.append(Utils.websafe(addr)) + + if unsubscribe_success: + msg = _('Successfully Unsubscribed:') + doc.AddItem(Div(Header(3, msg).Format() + + UnorderedList(*unsubscribe_success).Format(css='class="mailaddresses"') + ).Format(css='class="message success"')) + + if unsubscribe_errors: + msg = _('Cannot unsubscribe non-members:') + doc.AddItem(Div(Header(3, msg).Format() + + UnorderedList(*unsubscribe_errors).Format(css='class="mailaddresses"') + ).Format(css='class="message error"')) + + # See if this was a moderation bit operation + if cgidata.has_key('allmodbit_btn'): + val = cgidata.getvalue('allmodbit_val') + try: + val = int(val) + except VallueError: + val = None + if val not in (0, 1): + doc.addError(_('Bad moderation flag value')) + else: + for member in mlist.getMembers(): + mlist.setMemberOption(member, mm_cfg.Moderate, val) + # do the user options for members category + if cgidata.has_key('setmemberopts_btn') and cgidata.has_key('user'): + user = cgidata['user'] + if type(user) is ListType: + users = [] + for ui in range(len(user)): + users.append(urllib.unquote(user[ui].value)) + else: + users = [urllib.unquote(user.value)] + errors = [] + removes = [] + for user in users: + if cgidata.has_key('%s_unsub' % user): + try: + mlist.ApprovedDeleteMember(user, whence='member mgt page') + removes.append(user) + except Errors.NotAMemberError: + errors.append((user, _('Not subscribed'))) + continue + if not mlist.isMember(user): + doc.addMessage(_('Ignoring changes to deleted member: %(user)s'), + tag=_('Warning: '), + css='class="message warning"') + continue + value = cgidata.has_key('%s_digest' % user) + try: + mlist.setMemberOption(user, mm_cfg.Digests, value) + except (Errors.AlreadyReceivingDigests, + Errors.AlreadyReceivingRegularDeliveries, + Errors.CantDigestError, + Errors.MustDigestError): + # BAW: Hmm... + pass + + newname = cgidata.getvalue(user+'_realname', '') + newname = Utils.canonstr(newname, mlist.preferred_language) + mlist.setMemberName(user, newname) + + newlang = cgidata.getvalue(user+'_language') + oldlang = mlist.getMemberLanguage(user) + if Utils.IsLanguage(newlang) and newlang <> oldlang: + mlist.setMemberLanguage(user, newlang) + + moderate = not not cgidata.getvalue(user+'_mod') + mlist.setMemberOption(user, mm_cfg.Moderate, moderate) + + # Set the `nomail' flag, but only if the user isn't already + # disabled (otherwise we might change BYUSER into BYADMIN). + if cgidata.has_key('%s_nomail' % user): + if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED: + mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) + else: + mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) + for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): + opt_code = mm_cfg.OPTINFO[opt] + if cgidata.has_key('%s_%s' % (user, opt)): + mlist.setMemberOption(user, opt_code, 1) + else: + mlist.setMemberOption(user, opt_code, 0) + # Give some feedback on who's been removed + if removes: + msg = _('Successfully Removed:') + doc.AddItem(Div(Header(3, msg).Format() + + UnorderedList(*removes).Format(css='class="mailaddresses"') + ).Format(css='class="message success"')) + if errors: + msg = _('Error Unsubscribing:') + items = ['%s -- %s' % (x[0], x[1]) for x in errors] + doc.AddItem(Div(Header(3, msg).Format() + + UnorderedList(*items).Format(css='class="mailaddresses"') + ).Format(css='class="message error"')) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/admindb.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/admindb.py new file mode 100755 index 0000000..3355014 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/admindb.py @@ -0,0 +1,833 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Produce and process the pending-approval items for a list.""" + +import sys +import os +import cgi +import errno +import signal +import email +import time +from types import ListType +from urllib import quote_plus, unquote_plus + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman import Message +from Mailman import i18n +from Mailman.Handlers.Moderate import ModeratedMemberPost +from Mailman.ListAdmin import readMessage +from Mailman.Cgi import Auth +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +EMPTYSTRING = '' +NL = '\n' + +# Set up i18n. Until we know which list is being requested, we use the +# server's default. +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + +EXCERPT_HEIGHT = 10 +EXCERPT_WIDTH = 76 + + + +def helds_by_sender(mlist): + heldmsgs = mlist.GetHeldMessageIds() + bysender = {} + for id in heldmsgs: + sender = mlist.GetRecord(id)[1] + bysender.setdefault(sender, []).append(id) + return bysender + + +def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): + # We can't use a RadioButtonArray here because horizontal placement can be + # confusing to the user and vertical placement takes up too much + # real-estate. This is a hack! + btns = Table(css='class="radiobuttons center"') + btns.AddRow([text for text in labels]) + btns.AddRow([RadioButton(btnname, value, default) + for value, default in zip(values, defaults)]) + return btns + + + +def main(): + # Figure out which list is being requested + parts = Utils.GetPathPieces() + if not parts: + handle_no_list() + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + handle_no_list(_('No such list %(safelistname)s. ')) + syslog('error', 'No such list "%s": %s\n', listname, e) + return + + # Now that we know which list to use, set the system's language to it. + i18n.set_language(mlist.preferred_language) + + # Make sure the user is authorized to see this page. + cgidata = cgi.FieldStorage(keep_blank_values=1) + + if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + mm_cfg.AuthListModerator, + mm_cfg.AuthSiteAdmin), + cgidata.getvalue('adminpw', '')): + if cgidata.has_key('adminpw'): + # This is a re-authorization attempt + msg = _('Authorization failed.') + else: + msg = '' + msg = Paragraph(msg).Format(css='class="strong"') + Auth.loginpage(mlist, 'admindb', msg=msg) + return + + # Set up the results document + doc = Document() + doc.set_language(mlist.preferred_language) + + # See if we're requesting all the messages for a particular sender, or if + # we want a specific held message. + sender = None + msgid = None + details = None + envar = os.environ.get('QUERY_STRING') + if envar: + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = cgi.parse_qs(envar).get('sender') + if qs and type(qs) == ListType: + sender = qs[0] + qs = cgi.parse_qs(envar).get('msgid') + if qs and type(qs) == ListType: + msgid = qs[0] + qs = cgi.parse_qs(envar).get('details') + if qs and type(qs) == ListType: + details = qs[0] + + # We need a signal handler to catch the SIGTERM that can come from Apache + # when the user hits the browser's STOP button. See the comment in + # admin.py for details. + # + # BAW: Strictly speaking, the list should not need to be locked just to + # read the request database. However the request database asserts that + # the list is locked in order to load it and it's not worth complicating + # that logic. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + + realname = mlist.real_name + if not cgidata.keys(): + # If this is not a form submission (i.e. there are no keys in the + # form), then we don't need to do much special. + doc.SetTitle(_('%(realname)s Administrative Database')) + elif not details: + # This is a form submission + doc.SetTitle(_('%(realname)s Administrative Database Results')) + process_form(mlist, doc, cgidata) + # Now print the results and we're done. Short circuit for when there + # are no pending requests, but be sure to save the results! + if not mlist.NumRequestsPending(): + title = _('%(realname)s Administrative Database') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph( + _('There are no pending requests.') + + Link(mlist.GetScriptURL('admindb', absolute=1), + _('Click here to reload this page.')).Format() + )) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + mlist.Save() + return + + admindburl = mlist.GetScriptURL('admindb', absolute=1) + form = Form(admindburl) + # Add the instructions template + if details == 'instructions': + title = _('Detailed instructions for the administrative database') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + else: + title = _('Pending moderator requests') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + if details <> 'instructions': + form.AddItem(Paragraph( + SubmitButton('submit', _('Submit All Data'))).Format(css='class="mm_submit tied"')) + if not (sender or msgid): + form.AddItem(Paragraph( + CheckBox('discardalldefersp', 0).Format() + + _('Discard all messages marked Defer') + '.' + ).Format(css='class="mm_submit tied"')) + # Add a link back to the overview, if we're not viewing the overview! + adminurl = mlist.GetScriptURL('admin', absolute=1) + d = {'listname' : mlist.real_name, + 'detailsurl': admindburl + '?details=instructions', + 'summaryurl': admindburl, + 'viewallurl': admindburl + '?details=all', + 'adminurl' : adminurl, + 'filterurl' : adminurl + '/privacy/sender', + } + addform = 1 + if sender: + esender = Utils.websafe(sender) + d['description'] = _("all of %(esender)s's held messages.") + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_sender_requests(mlist, form, sender) + elif msgid: + d['description'] = _('a single held message.') + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_message_requests(mlist, form, msgid) + elif details == 'all': + d['description'] = _('all held messages.') + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_detailed_requests(mlist, form) + elif details == 'instructions': + doc.AddItem(Utils.maketext('admindbdetails.html', d, + raw=1, mlist=mlist)) + addform = 0 + else: + # Show a summary of all requests + doc.AddItem(Utils.maketext('admindbsummary.html', d, + raw=1, mlist=mlist)) + num = show_pending_subs(mlist, form) + num += show_pending_unsubs(mlist, form) + num += show_helds_overview(mlist, form) + addform = num > 0 + # Finish up the document, adding buttons to the form + if addform: + doc.AddItem(form) + if not (sender or msgid): + form.AddItem(Paragraph( + CheckBox('discardalldefersp', 0).Format() + + _('Discard all messages marked Defer') + '.' + ).Format(css='class="mm_submit tied"')) + form.AddItem(Paragraph(SubmitButton('submit', + _('Submit All Data')) + ).Format(css='class="mm_submit tied"')) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + # Commit all changes + mlist.Save() + finally: + mlist.Unlock() + + + +def handle_no_list(msg=''): + # Print something useful if no list was given. + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + title = _('Mailman Administrative Database Error') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(msg) + url = Utils.ScriptURL('admin', absolute=1) + link = Link(url, _('list of available mailing lists.')).Format() + doc.AddItem(_('You must specify a list name. Here is the %(link)s')) + doc.AddItem(MailmanLogo()) + print doc.Format() + + + +def show_pending_subs(mlist, form): + # Add the subscription request section + pendingsubs = mlist.GetSubscriptionIds() + if not pendingsubs: + return 0 + form.AddItem(Header(2, _('Subscription Requests'))) + table = Table(css='class="pending_subs"') + table.AddRow([_('Address/name'), + _('Your decision'), + _('Reason for refusal') + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="center strong header"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center strong header"') + table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="center strong header"') + # Alphabetical order by email address + byaddrs = {} + for id in pendingsubs: + addr = mlist.GetRecord(id)[1] + byaddrs.setdefault(addr, []).append(id) + addrs = byaddrs.keys() + addrs.sort() + num = 0 + highlight = 1 + for addr, ids in byaddrs.items(): + # Eliminate duplicates + for id in ids[1:]: + mlist.HandleRequest(id, mm_cfg.DISCARD) + id = ids[0] + time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) + fullname = Utils.uncanonstr(fullname, mlist.preferred_language) + radio = RadioButtonArray(id, (_('Defer'), + _('Approve'), + _('Reject'), + _('Discard')), + values=(mm_cfg.DEFER, + mm_cfg.SUBSCRIBE, + mm_cfg.REJECT, + mm_cfg.DISCARD), + checked=0).Format() + if addr not in mlist.ban_list: + radio += '
      ' + CheckBox('ban-%d' % id, 1).Format() + \ + ' ' + _('Permanently ban from this list') + # While the address may be a unicode, it must be ascii + paddr = addr.encode('us-ascii', 'replace') + table.AddRow(['%s
      %s' % (paddr, Utils.websafe(fullname)), + radio, + TextBox('comment-%d' % id, size=40) + ]) + if highlight: + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="left title"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left title"') + table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="left title"') + else: + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="left"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="left"') + highlight = not highlight + num += 1 + if num > 0: + form.AddItem(table) + return num + + + +def show_pending_unsubs(mlist, form): + # Add the pending unsubscription request section + lang = mlist.preferred_language + pendingunsubs = mlist.GetUnsubscriptionIds() + if not pendingunsubs: + return 0 + table = Table(css='class="pending_unsubs"') + table.AddRow([_('User address/name'), + _('Your decision'), + _('Reason for refusal') + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="center header strong"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center header strong"') + table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="center header strong"') + # Alphabetical order by email address + byaddrs = {} + for id in pendingunsubs: + addr = mlist.GetRecord(id)[1] + byaddrs.setdefault(addr, []).append(id) + addrs = byaddrs.keys() + addrs.sort() + num = 0 + highlight = 1 + for addr, ids in byaddrs.items(): + # Eliminate duplicates + for id in ids[1:]: + mlist.HandleRequest(id, mm_cfg.DISCARD) + id = ids[0] + addr = mlist.GetRecord(id) + try: + fullname = Utils.uncanonstr(mlist.getMemberName(addr), lang) + except Errors.NotAMemberError: + # They must have been unsubscribed elsewhere, so we can just + # discard this record. + mlist.HandleRequest(id, mm_cfg.DISCARD) + continue + table.AddRow(['%s
      %s' % (addr, Utils.websafe(fullname)), + RadioButtonArray(id, (_('Defer'), + _('Approve'), + _('Reject'), + _('Discard')), + values=(mm_cfg.DEFER, + mm_cfg.UNSUBSCRIBE, + mm_cfg.REJECT, + mm_cfg.DISCARD), + checked=0), + TextBox('comment-%d' % id, size=45) + ]) + if highlight: + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="left title"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center title"') + table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="left title"') + else: + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="left"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center"') + table.AddCellInfo(table.GetCurrentRowIndex(), 2, css='class="left"') + highlight = not highlight + num += 1 + if num > 0: + form.AddItem(Header(2, _('Unsubscription Requests'))) + form.AddItem(table) + return num + + + +def show_helds_overview(mlist, form): + # Sort the held messages by sender + bysender = helds_by_sender(mlist) + if not bysender: + return 0 + # Add the by-sender overview tables + admindburl = mlist.GetScriptURL('admindb', absolute=1) + table = Table(css='class="helds_overview"') + form.AddItem(Header(2, _('Postings Held for Approval'))) + form.AddItem(table) + senders = bysender.keys() + senders.sort() + for sender in senders: + qsender = quote_plus(sender) + esender = Utils.websafe(sender) + senderurl = admindburl + '?sender=' + qsender + # The encompassing sender table + stable = Table() + stable.AddRow([Paragraph(_('From: ') + esender)]) + stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2, css='class="header strong center"') + left = Table(css='class="helds_overview left"') + left.AddRow([_('Action to take on all these held messages:')]) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + btns = hacky_radio_buttons( + 'senderaction-' + qsender, + (_('Defer'), _('Accept'), _('Reject'), _('Discard')), + (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), + (1, 0, 0, 0)) + left.AddRow([btns]) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddRow([ + CheckBox('senderpreserve-' + qsender, 1).Format() + + _('Preserve messages for the site administrator') + ]) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddRow([ + CheckBox('senderforward-' + qsender, 1).Format() + + _('Forward messages (individually) to:') + ]) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddRow([ + TextBox('senderforwardto-' + qsender, + value=mlist.GetOwnerEmail(), size=47) + ]) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + # If the sender is a member and the message is being held due to a + # moderation bit, give the admin a chance to clear the member's mod + # bit. If this sender is not a member and is not already on one of + # the sender filters, then give the admin a chance to add this sender + # to one of the filters. + if mlist.isMember(sender): + if mlist.getMemberOption(sender, mm_cfg.Moderate): + left.AddRow([ + CheckBox('senderclearmodp-' + qsender, 1).Format() + + _("Clear this member's moderate flag.") + ]) + else: + left.AddRow( + [_('The sender is now a member of this list') + '.']) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + elif sender not in (mlist.accept_these_nonmembers + + mlist.hold_these_nonmembers + + mlist.reject_these_nonmembers + + mlist.discard_these_nonmembers): + left.AddRow([ + CheckBox('senderfilterp-' + qsender, 1).Format() + + _('Add %(esender)s to one of these sender filters:') + ]) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + btns = hacky_radio_buttons( + 'senderfilter-' + qsender, + (_('Accepts'), _('Holds'), _('Rejects'), _('Discards')), + (mm_cfg.ACCEPT, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD), + (0, 0, 0, 1)) + left.AddRow([btns]) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + if sender not in mlist.ban_list: + left.AddRow([ + CheckBox('senderbanp-' + qsender, 1).Format() + + _("""Ban %(esender)s from ever subscribing to this + mailing list""") + '.']) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + right = Table(css='class="helds_overview right"') + right.AddRow([ + _("""Click on the message number to view the individual + message, or you can """) + + Link(senderurl, _('view all messages from %(esender)s')).Format() + '.' + ]) + right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2) + counter = 1 + for id in bysender[sender]: + info = mlist.GetRecord(id) + ptime, sender, subject, reason, filename, msgdata = info + # BAW: This is really the size of the message pickle, which should + # be close, but won't be exact. Sigh, good enough. + try: + size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename)) + except OSError, e: + if e.errno <> errno.ENOENT: raise + # This message must have gotten lost, i.e. it's already been + # handled by the time we got here. + mlist.HandleRequest(id, mm_cfg.DISCARD) + continue + dispsubj = Utils.oneline( + subject, Utils.GetCharSet(mlist.preferred_language)) + t = Table(css='class="helds_overview right brief"') + t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), + _('Subject:'), + Utils.websafe(dispsubj) + ]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="description strong"') + t.AddRow([' ', _('Size:'), str(size) + _(' bytes')]) + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="description strong"') + if reason: + reason = _(reason) + else: + reason = _('not available') + t.AddRow([' ', _('Reason:'), reason]) + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="description strong"') + # Include the date we received the message, if available + when = msgdata.get('received_time') + if when: + t.AddRow([' ', _('Received:'), + time.ctime(when)]) + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="description strong"') + counter += 1 + right.AddRow([t]) + stable.AddRow([left, right]) + stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, css='class="helds_overview left"') + stable.AddCellInfo(stable.GetCurrentRowIndex(), 1, css='class="helds_overview right"') + table.AddRow([stable]) + return 1 + + + +def show_sender_requests(mlist, form, sender): + bysender = helds_by_sender(mlist) + if not bysender: + return + sender_ids = bysender.get(sender) + if sender_ids is None: + # BAW: should we print an error message? + return + total = len(sender_ids) + count = 1 + for id in sender_ids: + info = mlist.GetRecord(id) + show_post_requests(mlist, id, info, total, count, form) + count += 1 + + + +def show_message_requests(mlist, form, id): + try: + id = int(id) + info = mlist.GetRecord(id) + except (ValueError, KeyError): + # BAW: print an error message? + return + show_post_requests(mlist, id, info, 1, 1, form) + + + +def show_detailed_requests(mlist, form): + all = mlist.GetHeldMessageIds() + total = len(all) + count = 1 + for id in mlist.GetHeldMessageIds(): + info = mlist.GetRecord(id) + show_post_requests(mlist, id, info, total, count, form) + count += 1 + + + +def show_post_requests(mlist, id, info, total, count, form): + # For backwards compatibility with pre 2.0beta3 + if len(info) == 5: + ptime, sender, subject, reason, filename = info + msgdata = {} + else: + ptime, sender, subject, reason, filename, msgdata = info + # Header shown on each held posting (including count of total) + msg = _('Posting Held for Approval') + if total <> 1: + msg += _(' (%(count)d of %(total)d)') + form.AddItem(Header(2, msg)) + # We need to get the headers and part of the textual body of the message + # being held. The best way to do this is to use the email Parser to get + # an actual object, which will be easier to deal with. We probably could + # just do raw reads on the file. + try: + msg = readMessage(os.path.join(mm_cfg.DATA_DIR, filename)) + except IOError, e: + if e.errno <> errno.ENOENT: + raise + form.AddItem(Paragraph(_('Message with id #%(id)d was lost.') + ).Format(css='class="Italic"')) + # BAW: kludge to remove id from requests.db. + try: + mlist.HandleRequest(id, mm_cfg.DISCARD) + except Errors.LostHeldMessage: + pass + return + except email.Errors.MessageParseError: + form.AddItem(Paragraph(_('Message with id #%(id)d is corrupted.') + ).Format(css='class="Italic"')) + # BAW: Should we really delete this, or shuttle it off for site admin + # to look more closely at? + # BAW: kludge to remove id from requests.db. + try: + mlist.HandleRequest(id, mm_cfg.DISCARD) + except Errors.LostHeldMessage: + pass + return + # Get the header text and the message body excerpt + lines = [] + chars = 0 + # A negative value means, include the entire message regardless of size + limit = mm_cfg.ADMINDB_PAGE_TEXT_LIMIT + for line in email.Iterators.body_line_iterator(msg): + lines.append(line) + chars += len(line) + if chars > limit > 0: + break + # Negative values mean display the entire message, regardless of size + if limit > 0: + body = EMPTYSTRING.join(lines)[:mm_cfg.ADMINDB_PAGE_TEXT_LIMIT] + else: + body = EMPTYSTRING.join(lines) + # Get message charset and try encode in list charset + mcset = msg.get_param('charset', 'us-ascii').lower() + lcset = Utils.GetCharSet(mlist.preferred_language) + if mcset <> lcset: + try: + body = unicode(body, mcset).encode(lcset) + except (LookupError, UnicodeError, ValueError): + pass + hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in msg.items()]) + hdrtxt = Utils.websafe(hdrtxt) + # Okay, we've reconstituted the message just fine. Now for the fun part! + t = Table() + t.AddRow([_('From: '), sender]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') + t.AddRow([(_('Subject:')), + Utils.websafe(Utils.oneline(subject, lcset))]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') + t.AddRow([_('Reason:'), _(reason)]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') + when = msgdata.get('received_time') + if when: + t.AddRow([_('Received:'), time.ctime(when)]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') + # We can't use a RadioButtonArray here because horizontal placement can be + # confusing to the user and vertical placement takes up too much + # real-estate. This is a hack! + buttons = Table() + buttons.AddRow([RadioButton(id, mm_cfg.DEFER, 1).Format() + _('Defer'), + RadioButton(id, mm_cfg.APPROVE, 0).Format() + _('Approve'), + RadioButton(id, mm_cfg.REJECT, 0).Format() + _('Reject'), + RadioButton(id, mm_cfg.DISCARD, 0).Format() + _('Discard'), + ]) + t.AddRow([_('Action:'), + buttons.Format() + + CheckBox('preserve-%d' % id, 'on', 0).Format() + + _('Preserve message for site administrator') + '
      ' + + CheckBox('forward-%d' % id, 'on', 0).Format() + + _('Additionally, forward this message to: ') + + TextBox('forward-addr-%d' % id, size=47, value=mlist.GetOwnerEmail()).Format() + ]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') + notice = msgdata.get('rejection_notice', _('[No explanation given]')) + t.AddRow([ + _('If you reject this post, please explain (optional):'), + TextArea('comment-%d' % id, rows=4, cols=EXCERPT_WIDTH, + text = Utils.wrap(_(notice), column=80)) + ]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') + t.AddRow([_('Message Headers:'), + TextArea('headers-%d' % id, hdrtxt, + rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') + t.AddRow([_('Message Excerpt:'), + TextArea('fulltext-%d' % id, Utils.websafe(body), + rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)]) + t.AddCellInfo(t.GetCurrentRowIndex(), 0, css='class="description strong"') + t.AddCellInfo(t.GetCurrentRowIndex(), 1, css='class="value"') + form.AddItem(t) + + + +def process_form(mlist, doc, cgidata): + senderactions = {} + # Sender-centric actions + for k in cgidata.keys(): + for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-', + 'senderforwardto-', 'senderfilterp-', 'senderfilter-', + 'senderclearmodp-', 'senderbanp-'): + if k.startswith(prefix): + action = k[:len(prefix)-1] + sender = unquote_plus(k[len(prefix):]) + value = cgidata.getvalue(k) + senderactions.setdefault(sender, {})[action] = value + # discard-all-defers + try: + discardalldefersp = cgidata.getvalue('discardalldefersp', 0) + except ValueError: + discardalldefersp = 0 + for sender in senderactions.keys(): + actions = senderactions[sender] + # Handle what to do about all this sender's held messages + try: + action = int(actions.get('senderaction', mm_cfg.DEFER)) + except ValueError: + action = mm_cfg.DEFER + if action == mm_cfg.DEFER and discardalldefersp: + action = mm_cfg.DISCARD + if action in (mm_cfg.DEFER, mm_cfg.APPROVE, + mm_cfg.REJECT, mm_cfg.DISCARD): + preserve = actions.get('senderpreserve', 0) + forward = actions.get('senderforward', 0) + forwardaddr = actions.get('senderforwardto', '') + comment = _('No reason given') + bysender = helds_by_sender(mlist) + for id in bysender.get(sender, []): + try: + mlist.HandleRequest(id, action, comment, preserve, + forward, forwardaddr) + except (KeyError, Errors.LostHeldMessage): + # That's okay, it just means someone else has already + # updated the database while we were staring at the page, + # so just ignore it + continue + # Now see if this sender should be added to one of the nonmember + # sender filters. + if actions.get('senderfilterp', 0): + try: + which = int(actions.get('senderfilter')) + except ValueError: + # Bogus form + which = 'ignore' + if which == mm_cfg.ACCEPT: + mlist.accept_these_nonmembers.append(sender) + elif which == mm_cfg.HOLD: + mlist.hold_these_nonmembers.append(sender) + elif which == mm_cfg.REJECT: + mlist.reject_these_nonmembers.append(sender) + elif which == mm_cfg.DISCARD: + mlist.discard_these_nonmembers.append(sender) + # Otherwise, it's a bogus form, so ignore it + # And now see if we're to clear the member's moderation flag. + if actions.get('senderclearmodp', 0): + try: + mlist.setMemberOption(sender, mm_cfg.Moderate, 0) + except Errors.NotAMemberError: + # This person's not a member any more. Oh well. + pass + # And should this address be banned? + if actions.get('senderbanp', 0): + if sender not in mlist.ban_list: + mlist.ban_list.append(sender) + # Now, do message specific actions + banaddrs = [] + erroraddrs = [] + for k in cgidata.keys(): + formv = cgidata[k] + if type(formv) == ListType: + continue + try: + v = int(formv.value) + request_id = int(k) + except ValueError: + continue + if v not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, + mm_cfg.DISCARD, mm_cfg.SUBSCRIBE, mm_cfg.UNSUBSCRIBE, + mm_cfg.ACCEPT, mm_cfg.HOLD): + continue + # Get the action comment and reasons if present. + commentkey = 'comment-%d' % request_id + preservekey = 'preserve-%d' % request_id + forwardkey = 'forward-%d' % request_id + forwardaddrkey = 'forward-addr-%d' % request_id + bankey = 'ban-%d' % request_id + # Defaults + comment = _('[No reason given]') + preserve = 0 + forward = 0 + forwardaddr = '' + if cgidata.has_key(commentkey): + comment = cgidata[commentkey].value + if cgidata.has_key(preservekey): + preserve = cgidata[preservekey].value + if cgidata.has_key(forwardkey): + forward = cgidata[forwardkey].value + if cgidata.has_key(forwardaddrkey): + forwardaddr = cgidata[forwardaddrkey].value + # Should we ban this address? Do this check before handling the + # request id because that will evict the record. + if cgidata.getvalue(bankey): + sender = mlist.GetRecord(request_id)[1] + if sender not in mlist.ban_list: + mlist.ban_list.append(sender) + # Handle the request id + try: + mlist.HandleRequest(request_id, v, comment, + preserve, forward, forwardaddr) + except (KeyError, Errors.LostHeldMessage): + # That's okay, it just means someone else has already updated the + # database while we were staring at the page, so just ignore it + continue + except Errors.MMAlreadyAMember, v: + erroraddrs.append(v) + except Errors.MembershipIsBanned, pattern: + sender = mlist.GetRecord(request_id)[1] + banaddrs.append((sender, pattern)) + # save the list and print the results + doc.addMessage(_('Database Updated'), css='class="message success strong"') + if erroraddrs: + for addr in erroraddrs: + doc.AddItem(`addr` + _(' is already a member') + '
      ') + if banaddrs: + for addr, patt in banaddrs: + doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '
      ') diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/confirm.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/confirm.py new file mode 100644 index 0000000..90801da --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/confirm.py @@ -0,0 +1,752 @@ +# Copyright (C) 2001-2005 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Confirm a pending action via URL.""" + +import signal +import cgi +import time + +from Mailman import mm_cfg +from Mailman import Errors +from Mailman import i18n +from Mailman import MailList +from Mailman import Pending +from Mailman.UserDesc import UserDesc +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +def main(): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + parts = Utils.GetPathPieces() + if not parts or len(parts) < 1: + bad_confirmation(doc) + doc.AddItem(MailmanLogo()) + print doc.Format() + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + bad_confirmation(doc, _('No such list %(safelistname)s')) + doc.AddItem(MailmanLogo()) + print doc.Format() + syslog('error', 'No such list "%s": %s', listname, e) + return + + # Set the language for the list + i18n.set_language(mlist.preferred_language) + doc.set_language(mlist.preferred_language) + + # Get the form data to see if this is a second-step confirmation + cgidata = cgi.FieldStorage(keep_blank_values=1) + cookie = cgidata.getvalue('cookie') + if cookie == '': + ask_for_cookie(mlist, doc, _('Confirmation string was empty.')) + return + + if not cookie and len(parts) == 2: + cookie = parts[1] + + if len(parts) > 2: + bad_confirmation(doc) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + return + + if not cookie: + ask_for_cookie(mlist, doc) + return + + days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1) + 0.5) + confirmurl = mlist.GetScriptURL('confirm', absolute=1) + # Avoid cross-site scripting attacks + safecookie = Utils.websafe(cookie) + badconfirmstr = _('''Invalid confirmation string:%(safecookie)s.''') + '

      ' + badconfirmstr += '

      ' + _('''Note that confirmation strings expire approximately %(days)s days after the initial subscription request. If your confirmation has expired, please try to re-submit your subscription. Otherwise, re-enter your confirmation string.''') + + content = mlist.pend_confirm(cookie, expunge=False) + if content is None: + bad_confirmation(doc, badconfirmstr) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + return + + try: + if content[0] == Pending.SUBSCRIPTION: + if cgidata.getvalue('cancel'): + subscription_cancel(mlist, doc, cookie) + elif cgidata.getvalue('submit'): + subscription_confirm(mlist, doc, cookie, cgidata) + else: + subscription_prompt(mlist, doc, cookie, content[1]) + elif content[0] == Pending.UNSUBSCRIPTION: + try: + if cgidata.getvalue('cancel'): + unsubscription_cancel(mlist, doc, cookie) + elif cgidata.getvalue('submit'): + unsubscription_confirm(mlist, doc, cookie) + else: + unsubscription_prompt(mlist, doc, cookie, *content[1:]) + except Errors.NotAMemberError: + doc.addError(_('The address requesting unsubscription is not a member of the mailing list. Perhaps you have already been unsubscribed, e.g. by the list administrator?')) + # Expunge this record from the pending database. + expunge(mlist, cookie) + elif content[0] == Pending.CHANGE_OF_ADDRESS: + if cgidata.getvalue('cancel'): + addrchange_cancel(mlist, doc, cookie) + elif cgidata.getvalue('submit'): + addrchange_confirm(mlist, doc, cookie) + else: + # Watch out for users who have unsubscribed themselves in the + # meantime! + try: + addrchange_prompt(mlist, doc, cookie, *content[1:]) + except Errors.NotAMemberError: + doc.addError(_("""The address requesting to be changed has been subsequently unsubscribed. This request has been cancelled.""")) + # Expunge this record from the pending database. + expunge(mlist, cookie) + elif content[0] == Pending.HELD_MESSAGE: + if cgidata.getvalue('cancel'): + heldmsg_cancel(mlist, doc, cookie) + elif cgidata.getvalue('submit'): + heldmsg_confirm(mlist, doc, cookie) + else: + heldmsg_prompt(mlist, doc, cookie, *content[1:]) + elif content[0] == Pending.RE_ENABLE: + if cgidata.getvalue('cancel'): + reenable_cancel(mlist, doc, cookie) + elif cgidata.getvalue('submit'): + reenable_confirm(mlist, doc, cookie) + else: + reenable_prompt(mlist, doc, cookie, *content[1:]) + else: + bad_confirmation(doc, _('System error, bad content: %(content)s')) + except Errors.MMBadConfirmation: + bad_confirmation(doc, badconfirmstr) + + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + + + +def bad_confirmation(doc, extra=''): + title = _('Bad confirmation string') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(extra)) + + +def expunge(mlist, cookie): + # Expunge this record from the list's pending database. This requires + # that the list lock be acquired, however the list doesn't need to be + # saved because this operation doesn't touch the config.pck file. + mlist.Lock() + try: + mlist.pend_confirm(cookie, expunge=True) + finally: + mlist.Unlock() + + + +def ask_for_cookie(mlist, doc, extra=''): + title = _('Enter confirmation cookie') + doc.SetTitle(title) + if extra: + doc.AddItem(Div(Paragraph(extra)).Format(css='class="message error strong"')) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_("""Please enter the confirmation string (i.e. cookie) that you received in your email message, in the box below. Then hit the Submit button to proceed to the next confirmation step."""))) + form = Form(mlist.GetScriptURL('confirm', 1)) + table = Table() + + # Add cookie entry box + table.AddRow([_('Confirmation string:'), + TextBox('cookie')]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow([SubmitButton('submit_cookie', _('Submit'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') + form.AddItem(table) + doc.AddItem(form) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + + + +def subscription_prompt(mlist, doc, cookie, userdesc): + email = userdesc.address + password = userdesc.password + digest = userdesc.digest + lang = userdesc.language + name = Utils.uncanonstr(userdesc.fullname, lang) + i18n.set_language(lang) + doc.set_language(lang) + form = Form(mlist.GetScriptURL('confirm', 1)) + table = Table() + + title = _('Confirm subscription request') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + + listname = mlist.real_name + # This is the normal, no-confirmation required results text. + # + # We do things this way so we don't have to reformat this paragraph, which + # would mess up translations. If you modify this text for other reasons, + # please refill the paragraph, and clean up the logic. + + doc.AddItem(Paragraph(_('''Your confirmation is required in order to complete the subscription request to the mailing list %(listname)s. Your subscription settings are shown below; make any necessary changes and hit Subscribe to complete the confirmation process. Once you've confirmed your subscription request, you will be shown your account options page which you can use to further customize your membership options.'''))) + + doc.AddItem(Paragraph(_('''Note: your password will be emailed to you once your subscription is confirmed. You can change it by visiting your personal options page.'''))) + + doc.AddItem(Paragraph(_('''Or hit Cancel my subscription request if you no longer want to subscribe to this list.'''))) + + if mlist.subscribe_policy in (2, 3): + # Confirmation is required + AddItem(Paragraph(_("""Your confirmation is required in order to continue with the subscription request to the mailing list %(listname)s. Your subscription settings are shown below; make any necessary changes and hit Subscribe to list ... to complete the confirmation process. Once you've confirmed your subscription request, the moderator must approve or reject your membership request. You will receive notice of their decision."""))) + + doc.AddItem(Paragraph(_('''Note: your password will be emailed to you once your subscription is confirmed. You can change it by visiting your personal options page.'''))) + + doc.AddItem(Paragraph(_('''Or, if you've changed your mind and do not want to subscribe to this mailing list, you can hit Cancel my subscription request.'''))) + + table.AddRow([_('Your email address:'), email]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + table.AddRow([_('Your real name:'), + TextBox('realname', name)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') +## table.AddRow([Label(_('Password:')), +## PasswordBox('password', password)]) +## table.AddRow([Label(_('Password (confirm):')), +## PasswordBox('pwconfirm', password)]) + # Only give them a choice to receive digests if they actually have a + # choice . + if mlist.nondigestable and mlist.digestable: + table.AddRow([_('Receive digests?'), + RadioButtonArray('digests', (_('No'), _('Yes')), + checked=digest, values=(0, 1))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + langs = mlist.GetAvailableLanguages() + values = [_(Utils.GetLanguageDescr(l)) for l in langs] + try: + selected = langs.index(lang) + except ValueError: + selected = lang.index(mlist.preferred_language) + table.AddRow([_('Preferred language:'), + SelectOptions('language', langs, values, selected)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + table.AddRow([Hidden('cookie', cookie)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([ + SubmitButton('cancel', _('Cancel my subscription request')), + SubmitButton('submit', _('Subscribe to list %(listname)s')) + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') + form.AddItem(table) + doc.AddItem(form) + + + +def subscription_cancel(mlist, doc, cookie): + mlist.Lock() + try: + # Discard this cookie + userdesc = mlist.pend_confirm(cookie)[1] + finally: + mlist.Unlock() + lang = userdesc.language + i18n.set_language(lang) + doc.set_language(lang) + title = _('Subscription request canceled') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_('You have canceled your subscription request.'))) + + + +def subscription_confirm(mlist, doc, cookie, cgidata): + # See the comment in admin.py about the need for the signal + # handler. + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + listname = mlist.real_name + mlist.Lock() + try: + try: + # Some pending values may be overridden in the form. email of + # course is hardcoded. ;) + lang = cgidata.getvalue('language') + if not Utils.IsLanguage(lang): + lang = mlist.preferred_language + i18n.set_language(lang) + doc.set_language(lang) + if cgidata.has_key('digests'): + try: + digest = int(cgidata.getvalue('digests')) + except ValueError: + digest = None + else: + digest = None + userdesc = mlist.pend_confirm(cookie, expunge=False)[1] + fullname = cgidata.getvalue('realname', None) + if fullname is not None: + fullname = Utils.canonstr(fullname, lang) + overrides = UserDesc(fullname=fullname, digest=digest, lang=lang) + userdesc += overrides + op, addr, pw, digest, lang = mlist.ProcessConfirmation( + cookie, userdesc) + except Errors.MMNeedApproval: + title = _('Awaiting moderator approval') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_("""You have successfully confirmed your subscription request to the mailing list %(listname)s, however final approval is required from the list moderator before you will be subscribed. Your request has been forwarded to the list moderator, and you will be notified of the moderator's decision."""))) + except Errors.NotAMemberError: + bad_confirmation(doc, _('''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) + except Errors.MMAlreadyAMember: + doc.addError(_("You are already a member of this mailing list!")) + except Errors.MembershipIsBanned: + owneraddr = mlist.GetOwnerEmail() + doc.addError(_("""You are currently banned from subscribing to this list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.""")) + except Errors.HostileSubscriptionError: + doc.addError(_("""You were not invited to this mailing list. The invitation has been discarded, and both list administrators have been alerted.""")) + else: + # Use the user's preferred language + i18n.set_language(lang) + doc.set_language(lang) + # The response + listname = mlist.real_name + title = _('Subscription request confirmed') + optionsurl = mlist.GetOptionsURL(addr, absolute=1) + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_('''You have successfully confirmed your subscription request for "%(addr)s" to the %(listname)s mailing list. A separate confirmation message will be sent to your email address, along with your password, and other useful information and links.'''))) + + doc.AddItem(Paragraph(_('''You can now proceed to your membership login page.'''))) + mlist.Save() + finally: + mlist.Unlock() + + + +def unsubscription_cancel(mlist, doc, cookie): + # Expunge this record from the pending database + expunge(mlist, cookie) + title = _('Unsubscription request canceled') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_('You have canceled your unsubscription request.'))) + + + +def unsubscription_confirm(mlist, doc, cookie): + # See the comment in admin.py about the need for the signal + # handler. + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + mlist.Lock() + try: + try: + # Do this in two steps so we can get the preferred language for + # the user who is unsubscribing. + op, addr = mlist.pend_confirm(cookie, expunge=False) + lang = mlist.getMemberLanguage(addr) + i18n.set_language(lang) + doc.set_language(lang) + op, addr = mlist.ProcessConfirmation(cookie) + except Errors.NotAMemberError: + bad_confirmation(doc, _('''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) + else: + # The response + listname = mlist.real_name + title = _('Unsubscription request confirmed') + listinfourl = mlist.GetScriptURL('listinfo', absolute=1) + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_("""You have successfully unsubscribed from the %(listname)s mailing list. You can now visit the list's main information page."""))) + mlist.Save() + finally: + mlist.Unlock() + + + +def unsubscription_prompt(mlist, doc, cookie, addr): + # Set language + lang = mlist.getMemberLanguage(addr) + i18n.set_language(lang) + doc.set_language(lang) + + # Set title + title = _('Confirm unsubscription request') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + + # Set listname and fullname + listname = mlist.real_name + fullname = mlist.getMemberName(addr) + if fullname is None: + fullname = _('Not available.') + else: + fullname = Utils.uncanonstr(fullname, lang) + doc.AddItem(Paragraph(_('Your confirmation is required in order to complete the unsubscription request from the mailing list %(listname)s. You are currently subscribed with:'))) + doc.AddItem(''' +

        +
      • ''' + _('Real name:') + ''' ''' + fullname + '''
      • +
      • ''' + _('Email address:') + ''' ''' + addr + '''
      • +
      + ''') + doc.AddItem(Paragraph(_('''Hit the Unsubscribe button below to complete the confirmation process.'''))) + doc.AddItem(Paragraph(_('''Or hit Cancel and discard to cancel this unsubscription request.'''))) + + # Set Form + form = Form(mlist.GetScriptURL('confirm', 1)) + table = Table() + table.AddRow([Hidden('cookie', cookie)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([SubmitButton('submit', _('Unsubscribe')), + SubmitButton('cancel', _('Cancel and discard'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') + + form.AddItem(table) + doc.AddItem(form) + + + +def addrchange_cancel(mlist, doc, cookie): + # Expunge this record from the pending database + expunge(mlist, cookie) + title = 'Change of address request canceled' + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_('You have canceled your change of address request.'))) + + + +def addrchange_confirm(mlist, doc, cookie): + # See the comment in admin.py about the need for the signal + # handler. + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + mlist.Lock() + try: + try: + # Do this in two steps so we can get the preferred language for + # the user who is unsubscribing. + op, oldaddr, newaddr, globally = mlist.pend_confirm( + cookie, expunge=False) + lang = mlist.getMemberLanguage(oldaddr) + i18n.set_language(lang) + doc.set_language(lang) + op, oldaddr, newaddr = mlist.ProcessConfirmation(cookie) + except Errors.NotAMemberError: + bad_confirmation(doc, _('''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) + except Errors.MembershipIsBanned: + owneraddr = mlist.GetOwnerEmail() + realname = mlist.real_name + doc.addError(_("""%(newaddr)s is banned from subscribing to the %(realname)s list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.""")) + else: + # The response + listname = mlist.real_name + title = _('Change of address request confirmed') + optionsurl = mlist.GetOptionsURL(newaddr, absolute=1) + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_("""You have successfully changed your address on the %(listname)s mailing list from %(oldaddr)s to %(newaddr)s."""))) + doc.AddItem(Paragraph(_('''You can now proceed to your membership login page.'''))) + mlist.Save() + finally: + mlist.Unlock() + + + +def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): + # Set Language + lang = mlist.getMemberLanguage(oldaddr) + i18n.set_language(lang) + doc.set_language(lang) + # Set Title + title = _('Confirm change of address request') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + # Set Listname and Fullname + listname = mlist.real_name + fullname = mlist.getMemberName(oldaddr) + if fullname is None: + fullname = _('Not available') + else: + fullname = Utils.uncanonstr(fullname, lang) + if globally: + globallys = _('globally') + else: + globallys = '' + + # Set Description + doc.AddItem(Paragraph(_('''Your confirmation is required in order to complete the change of address request for the mailing list %(listname)s. You are currently subscribed with:'''))) + doc.AddItem(_(''' +
        +
      • ''' + _('Real name:') + ''' %(fullname)s
      • +
      • ''' + _('Old email address:') + ''' %(oldaddr)s
      • +
      + ''')) + doc.AddItem(Paragraph(_('''and you have requested to %(globallys)s change your email address to:'''))) + doc.AddItem(_(''' +
        +
      • ''' + _('New email address:') + ''' %(newaddr)s
      • +
      + ''')) + doc.AddItem(Paragraph(_('''Hit the Change address button below to complete the confirmation process. Or hit Cancel and discard to cancel this change of address request.'''))) + + form = Form(mlist.GetScriptURL('confirm', 1)) + table = Table() + table.AddRow([Hidden('cookie', cookie)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([SubmitButton('submit', _('Change address')), + SubmitButton('cancel', _('Cancel and discard'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') + form.AddItem(table) + doc.AddItem(form) + + + +def heldmsg_cancel(mlist, doc, cookie): + title = _('Continue awaiting approval') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + # Expunge this record from the pending database. + expunge(mlist, cookie) + doc.AddItem(Paragraph('''Okay, the list moderator will still have the opportunity to approve or reject this message.''')) + + + +def heldmsg_confirm(mlist, doc, cookie): + # See the comment in admin.py about the need for the signal + # handler. + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + mlist.Lock() + try: + try: + # Do this in two steps so we can get the preferred language for + # the user who posted the message. + op, id = mlist.pend_confirm(cookie) + ign, sender, msgsubject, ign, ign, ign = mlist.GetRecord(id) + subject = Utils.websafe(msgsubject) + lang = mlist.getMemberLanguage(sender) + i18n.set_language(lang) + doc.set_language(lang) + # Discard the message + mlist.HandleRequest(id, mm_cfg.DISCARD, + _('Sender discarded message via web.')) + except Errors.LostHeldMessage: + bad_confirmation(doc, Paragraph(_('''The held message with the Subject: header %(subject)s could not be found. The most likely reason for this is that the list moderator has already approved or rejected the message. You were not able to cancel it in time.'''))) + else: + # The response + listname = mlist.real_name + title = _('Posted message canceled') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_('''You have successfully canceled the posting of your message with the Subject: %(subject)s to the mailing list %(listname)s.'''))) + mlist.Save() + finally: + mlist.Unlock() + + + +def heldmsg_prompt(mlist, doc, cookie, id): + title = _('Cancel held message posting') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + # Blarg. The list must be locked in order to interact with the ListAdmin + # database, even for read-only. See the comment in admin.py about the + # need for the signal handler. + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + # Get the record, but watch for KeyErrors which mean the admin has already + # disposed of this message. + mlist.Lock() + try: + try: + data = mlist.GetRecord(id) + except KeyError: + data = None + finally: + mlist.Unlock() + + if data is None: + bad_confirmation(doc, Paragraph(_("""The held message you were referred to has already been handled by the list administrator."""))) + return + + # Unpack the data and present the confirmation message + ign, sender, msgsubject, givenreason, ign, ign = data + # Now set the language to the sender's preferred. + lang = mlist.getMemberLanguage(sender) + i18n.set_language(lang) + doc.set_language(lang) + subject = Utils.websafe(msgsubject) + reason = Utils.websafe(_(givenreason)) + listname = mlist.real_name + doc.AddItem(Paragraph(_('Your confirmation is required in order to cancel the posting of your message to the mailing list %(listname)s:'))) + doc.AddItem(''' +
      • ''' + _('Sender:') + ''' ''' + sender + '''
      • +
      • ''' + _('Subject:') + ''' ''' + subject + '''
      • +
      • ''' + _('Reason:') + ''' ''' + reason + '''
      • +
      + ''') + doc.AddItem(Paragraph('''Hit the Cancel posting button to discard the posting. Or hit the Continue awaiting approval button to continue to allow the list moderator to approve or reject the message.''')) + form = Form(mlist.GetScriptURL('confirm', 1)) + table = Table() + table.AddRow([Hidden('cookie', cookie)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([SubmitButton('submit', _('Cancel posting')), + SubmitButton('cancel', _('Continue awaiting approval'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') + + form.AddItem(table) + doc.AddItem(form) + + + +def reenable_cancel(mlist, doc, cookie): + # Don't actually discard this cookie, since the user may decide to + # re-enable their membership at a future time, and we may be sending out + # future notifications with this cookie value. + title = _('Membership re-enabeling canceled') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_("""You have canceled the re-enabling of your membership. If we continue to receive bounces from your address, it could be deleted from this mailing list."""))) + + + +def reenable_confirm(mlist, doc, cookie): + # See the comment in admin.py about the need for the signal + # handler. + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + mlist.Lock() + try: + try: + # Do this in two steps so we can get the preferred language for + # the user who is unsubscribing. + op, listname, addr = mlist.pend_confirm(cookie, expunge=False) + lang = mlist.getMemberLanguage(addr) + i18n.set_language(lang) + doc.set_language(lang) + op, addr = mlist.ProcessConfirmation(cookie) + except Errors.NotAMemberError: + bad_confirmation(doc, _('''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) + else: + # The response + listname = mlist.real_name + optionsurl = mlist.GetOptionsURL(addr, absolute=1) + title = _('Membership re-enabled') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_("""You have successfully re-enabled your membership in the %(listname)s mailing list. You can now visit your member options page."""))) + mlist.Save() + finally: + mlist.Unlock() + + + +def reenable_prompt(mlist, doc, cookie, list, member): + title = _('Re-enable mailing list membership') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + + lang = mlist.getMemberLanguage(member) + i18n.set_language(lang) + doc.set_language(lang) + + realname = mlist.real_name + info = mlist.getBounceInfo(member) + + if not info: + listinfourl = mlist.GetScriptURL('listinfo', absolute=1) + # They've already be unsubscribed + doc.AddItem(Paragraph(_("""We're sorry, but you have already been unsubscribed from this mailing list. To re-subscribe, please visit the list information page."""))) + return + + date = time.strftime('%A, %B %d, %Y', + time.localtime(time.mktime(info.date + (0,)*6))) + daysleft = int(info.noticesleft * + mlist.bounce_you_are_disabled_warnings_interval / + mm_cfg.days(1)) + # BAW: for consistency this should be changed to 'fullname' or the above + # 'fullname's should be changed to 'username'. Don't want to muck with + # the i18n catalogs though. + username = mlist.getMemberName(member) + if username is None: + username = _('Not available') + else: + username = Utils.uncanonstr(username, lang) + + doc.AddItem(Paragraph(_('Your membership in the %(realname)s mailing list is currently disabled due to excessive bounces. Your confirmation is required in order to re-enable delivery to your address. We have the following information on file:'))) + doc.AddItem(''' +
      • ''' + _('Member address:') + ''' ''' + member + '''
      • +
      • ''' + _('Member name:') + ''' ''' + username + '''
      • +
      • ''' + _('Last bounce received on:') + ''' ''' + date + '''
      • +
      • ''' + _('Approximate number of days before you are permanently removed from this list:') + ''' ''' + daysleft + '''
      • +
      + ''') + doc.AddItem(Paragraph(_('''Hit the Re-enable membership button to resume receiving postings from the mailing list. Or hit the Cancel button to defer re-enabling your membership.'''))) + + form = Form(mlist.GetScriptURL('confirm', 1)) + table = Table() + table.AddRow([Hidden('cookie', cookie)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([SubmitButton('submit', _('Re-enable membership')), + SubmitButton('cancel', _('Cancel'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="mm_submit"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="mm_submit"') + + form.AddItem(table) + doc.AddItem(form) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/create.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/create.py new file mode 100755 index 0000000..95641f6 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/create.py @@ -0,0 +1,416 @@ +# Copyright (C) 2001-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Create mailing lists through the web.""" + +import sys +import os +import signal +import cgi +import sha +from types import ListType + +from Mailman import mm_cfg +from Mailman import MailList +from Mailman import Message +from Mailman import Errors +from Mailman import i18n +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + + +def main(): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + cgidata = cgi.FieldStorage() + parts = Utils.GetPathPieces() + if parts: + # Bad URL specification + title = _('Bad URL specification') + doc.SetTitle(title) + doc.AddItem(Div(Paragraph(title)).Format(css='class="message error strong"')) + syslog('error', 'Bad URL specification: %s', parts) + elif cgidata.has_key('doit'): + # We must be processing the list creation request + process_request(doc, cgidata) + elif cgidata.has_key('clear'): + request_creation(doc) + else: + # Put up the list creation request form + request_creation(doc) + # Always add the footer and print the document + doc.AddItem(Paragraph( + _('Return to the ') + + Link(Utils.ScriptURL('listinfo'), + _('general list overview')).Format() + + '
      ' + _('Return to the ') + + Link(Utils.ScriptURL('admin'), + _('administrative list overview')).Format()).Format()) + doc.AddItem(MailmanLogo()) + print doc.Format() + + + +def process_request(doc, cgidata): + # Lowercase the listname since this is treated as the "internal" name. + listname = cgidata.getvalue('listname', '').strip().lower() + owner = cgidata.getvalue('owner', '').strip() + try: + autogen = int(cgidata.getvalue('autogen', '0')) + except ValueError: + autogen = 0 + try: + notify = int(cgidata.getvalue('notify', '0')) + except ValueError: + notify = 0 + try: + moderate = int(cgidata.getvalue('moderate', + mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) + except ValueError: + moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION + + password = cgidata.getvalue('password', '').strip() + confirm = cgidata.getvalue('confirm', '').strip() + auth = cgidata.getvalue('auth', '').strip() + langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) + + if not isinstance(langs, ListType): + langs = [langs] + # Sanity check + safelistname = Utils.websafe(listname) + if '@' in listname: + request_creation(doc, cgidata, + _('List name must not include "@": %(safelistname)s')) + return + if Utils.list_exists(listname): + # BAW: should we tell them the list already exists? This could be + # used to mine/guess the existance of non-advertised lists. Then + # again, that can be done in other ways already, so oh well. + request_creation(doc, cgidata, + _('List already exists: %(safelistname)s')) + return + if not listname: + request_creation(doc, cgidata, + _('You forgot to enter the list name')) + return + if not owner: + request_creation(doc, cgidata, + _('You forgot to specify the list owner')) + return + + if autogen: + if password or confirm: + request_creation( + doc, cgidata, + _('Leave the initial password (and confirmation) fields blank if you want Mailman to autogenerate the list passwords.')) + return + password = confirm = Utils.MakeRandomPassword( + mm_cfg.ADMIN_PASSWORD_LENGTH) + else: + if password <> confirm: + request_creation(doc, cgidata, + _('Initial list passwords do not match')) + return + if not password: + request_creation( + doc, cgidata, + # The little tag is used so that this string + # differs from the one in bin/newlist. The former is destined + # for the web while the latter is destined for email, so they + # must be different entries in the message catalog. + _('The list password cannot be empty')) + return + # The authorization password must be non-empty, and it must match either + # the list creation password or the site admin password + ok = 0 + if auth: + ok = Utils.check_global_password(auth, 0) + if not ok: + ok = Utils.check_global_password(auth) + if not ok: + request_creation( + doc, cgidata, + _('You are not authorized to create new mailing lists')) + return + # Make sure the web hostname matches one of our virtual domains + hostname = Utils.get_domain() + if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ + not mm_cfg.VIRTUAL_HOSTS.has_key(hostname): + safehostname = Utils.websafe(hostname) + request_creation(doc, cgidata, + _('Unknown virtual host: %(safehostname)s')) + return + emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST) + # We've got all the data we need, so go ahead and try to create the list + # See admin.py for why we need to set up the signal handler. + mlist = MailList.MailList() + + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + + pw = sha.new(password).hexdigest() + # Guarantee that all newly created files have the proper permission. + # proper group ownership should be assured by the autoconf script + # enforcing that all directories have the group sticky bit set + oldmask = os.umask(002) + try: + try: + mlist.Create(listname, owner, pw, langs, emailhost) + finally: + os.umask(oldmask) + except Errors.EmailAddressError, e: + if e.args: + s = Utils.websafe(e.args[0]) + else: + s = Utils.websafe(owner) + request_creation(doc, cgidata, + _('Bad owner email address: %(s)s')) + return + except Errors.MMListAlreadyExistsError: + # MAS: List already exists so we don't need to websafe it. + request_creation(doc, cgidata, + _('List already exists: %(listname)s')) + return + except Errors.BadListNameError, e: + if e.args: + s = Utils.websafe(e.args[0]) + else: + s = Utils.websafe(listname) + request_creation(doc, cgidata, + _('Illegal list name: %(s)s')) + return + except Errors.MMListError: + request_creation( + doc, cgidata, + _('''Some unknown error occurred while creating the list. Please contact the site administrator for assistance.''')) + return + + # Initialize the host_name and web_page_url attributes, based on + # virtual hosting settings and the request environment variables. + mlist.default_member_moderation = moderate + mlist.web_page_url = mm_cfg.DEFAULT_URL_PATTERN % hostname + mlist.host_name = emailhost + mlist.Save() + finally: + # Now be sure to unlock the list. It's okay if we get a signal here + # because essentially, the signal handler will do the same thing. And + # unlocking is unconditional, so it's not an error if we unlock while + # we're already unlocked. + mlist.Unlock() + + # Now do the MTA-specific list creation tasks + if mm_cfg.MTA: + modname = 'Mailman.MTA.' + mm_cfg.MTA + __import__(modname) + sys.modules[modname].create(mlist, cgi=1) + + # And send the notice to the list owner. + if notify: + siteowner = Utils.get_site_email(mlist.host_name, 'owner') + text = Utils.maketext( + 'newlist.txt', + {'listname' : listname, + 'password' : password, + 'admin_url' : mlist.GetScriptURL('admin', absolute=1), + 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), + 'requestaddr' : mlist.GetRequestEmail(), + 'siteowner' : siteowner, + }, mlist=mlist) + msg = Message.UserNotification( + owner, siteowner, + _('Your new mailing list: %(listname)s'), + text, mlist.preferred_language) + msg.send(mlist) + + # Success! + listinfo_url = mlist.GetScriptURL('listinfo', absolute=1) + admin_url = mlist.GetScriptURL('admin', absolute=1) + create_url = Utils.ScriptURL('create') + + title = _('Mailing list creation results') + doc.SetTitle(title) + #doc.AddItem(Header(1, title)) + doc.addMessage(_('''You have successfully created the mailing list %(listname)s. A notification has been sent to the list owner %(owner)s.'''), css='class="message success strong"') + doc.AddItem(Paragraph(_('You can now:'))) + ullist = UnorderedList() + ullist.AddItem(Link(listinfo_url, _("Visit the list's info page"))) + ullist.AddItem(Link(admin_url, _("Visit the list's admin page"))) + ullist.AddItem(Link(create_url, _('Create another list'))) + doc.AddItem(ullist) + + +# Because the cgi module blows +class Dummy: + def getvalue(self, name, default): + return default +dummy = Dummy() + + + +def request_creation(doc, cgidata=dummy, errmsg=None): + # What virtual domain are we using? + hostname = Utils.get_domain() + # Set up the document + title = _('Create a %(hostname)s Mailing List') + table = Table() + doc.SetTitle(title) + # Add any error message + if errmsg: + doc.AddItem(Div(Paragraph(errmsg)).Format(css='class="message error strong"')) + + # Add header + doc.AddItem(Header(1, title)) + + # Add description + doc.AddItem(Paragraph(_('''You can create a new mailing list by entering the relevant information into the form below. The name of the mailing list will be used as the primary address for posting messages to the list, so it should be lowercased. You will not be able to change this once the list is created.'''))) + + doc.AddItem(Paragraph(_('''You also need to enter the email address of the initial list owner. Once the list is created, the list owner will be given notification, along with the initial list password. The list owner will then be able to modify the password and add or remove additional list owners.'''))) + + doc.AddItem(Paragraph(_('''If you want Mailman to automatically generate the initial list admin password, click on `Yes' in the autogenerate field below, and leave the initial list password fields empty.'''))) + + doc.AddItem(Paragraph(_('''You must have the proper authorization to create new mailing lists. Each site should have a list creator's password, which you can enter in the field at the bottom. Note that the site administrator's password can also be used for authentication.'''))) + + # Build the form for the necessary input + GREY = mm_cfg.WEB_ADMINITEM_COLOR + form = Form(Utils.ScriptURL('create')) + table = Table() + + table.AddRow([Header(3, _('List Identity'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') + + listname = cgidata.getvalue('listname', '') + # MAS: Don't websafe twice. TextBox does it. + table.AddRow([_('Name of list:'), + TextBox('listname', listname)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + owner = cgidata.getvalue('owner', '') + # MAS: Don't websafe twice. TextBox does it. + table.AddRow([_('Initial list owner address:'), + TextBox('owner', owner)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + try: + autogen = int(cgidata.getvalue('autogen', '0')) + except ValueError: + autogen = 0 + table.AddRow([_('Auto-generate initial list password?'), + RadioButtonArray('autogen', (_('No'), _('Yes')), + checked=autogen, + values=(0, 1))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + safepasswd = Utils.websafe(cgidata.getvalue('password', '')) + table.AddRow([_('Initial list password:'), + PasswordBox('password', safepasswd)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + safeconfirm = Utils.websafe(cgidata.getvalue('confirm', '')) + table.AddRow([_('Confirm initial password:'), + PasswordBox('confirm', safeconfirm)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + try: + notify = int(cgidata.getvalue('notify', '1')) + except ValueError: + notify = 1 + try: + moderate = int(cgidata.getvalue('moderate', + mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) + except ValueError: + moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION + + table.AddRow([Header(3,_('List Characteristics'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') + + table.AddRow([ + _("""Should new members be quarantined before they are allowed to post unmoderated to this list? Answer Yes to hold new member postings for moderator approval by default."""), + RadioButtonArray('moderate', (_('No'), _('Yes')), + checked=moderate, + values=(0,1))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + # Create the table of initially supported languages, sorted on the long + # name of the language. + revmap = {} + for key, (name, charset) in mm_cfg.LC_DESCRIPTIONS.items(): + revmap[_(name)] = key + langnames = revmap.keys() + langnames.sort() + langs = [] + for name in langnames: + langs.append(revmap[name]) + try: + langi = langs.index(mm_cfg.DEFAULT_SERVER_LANGUAGE) + except ValueError: + # Someone must have deleted the servers's preferred language. Could + # be other trouble lurking! + langi = 0 + # BAW: we should preserve the list of checked languages across form + # invocations. + checked = [0] * len(langs) + checked[langi] = 1 + deflang = _(Utils.GetLanguageDescr(mm_cfg.DEFAULT_SERVER_LANGUAGE)) + table.AddRow([_('Initial list of supported languages.') + + Paragraph(_('''Note that if you do not select at least one initial language, the list will use the server default language of %(deflang)s.''')).Format(), + CheckBoxArray('langs', + [_(Utils.GetLanguageDescr(L)) for L in langs], + checked=checked, + values=langs)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow([_('Send "list created" email to list owner?'), + RadioButtonArray('notify', (_('No'), _('Yes')), + checked=notify, + values=(0, 1))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([_("List creator's (authentication) password:"), + PasswordBox('auth')]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow([SubmitButton('doit', _('Create List')), + SubmitButton('clear', _('Clear Form'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="center"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="center"') + + form.AddItem(table) + doc.AddItem(form) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/edithtml.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/edithtml.py new file mode 100755 index 0000000..a7e6998 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/edithtml.py @@ -0,0 +1,188 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Script which implements admin editing of the list's html templates.""" + +import os +import cgi +import errno +import re + +from Mailman import Utils +from Mailman import MailList +from Mailman.htmlformat import * +from Mailman.HTMLFormatter import HTMLFormatter +from Mailman import Errors +from Mailman.Cgi import Auth +from Mailman.Logging.Syslog import syslog +from Mailman import i18n + +_ = i18n._ + + + +def main(): + # Trick out pygettext since we want to mark template_data as translatable, + # but we don't want to actually translate it here. + def _(s): + return s + + template_data = ( + ('listinfo.html', _('General list information page')), + ('subscribe.html', _('Subscribe results page')), + ('options.html', _('User specific options page')), + ('subscribeack.txt', _('Welcome email text file')), + ) + + _ = i18n._ + doc = Document() + + # Set up the system default language + i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + parts = Utils.GetPathPieces() + if not parts: + title = _('List name is required') + doc.SetTitle(title) + doc.AddItem(Div(Paragraph( + _('%(title)s')) + ).Format(css='class="message error strong"')) + doc.AddItem(MailmanLogo()) + print doc.Format() + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + title = _('No such list') + doc.SetTitle(title) + doc.AddItem(Div(Header(3, title), + Paragraph( + _('The %(safelistname)s mailing list does not exist.')) + ).Format(css='class="message error"')) + doc.AddItem(MailmanLogo()) + print doc.Format() + syslog('error', 'No such list "%s": %s', listname, e) + return + + # Now that we have a valid list, set the language to its default + i18n.set_language(mlist.preferred_language) + doc.set_language(mlist.preferred_language) + + # Must be authenticated to get any farther + cgidata = cgi.FieldStorage() + + # Editing the html for a list is limited to the list admin and site admin. + if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + cgidata.getvalue('adminpw', '')): + if cgidata.has_key('admlogin'): + # This is a re-authorization attempt + msg = Div(Paragraph(_('Authorization failed.'))).Format(css='class="message error strong"') + else: + msg = '' + Auth.loginpage(mlist, 'admin', msg=msg) + return + + realname = mlist.real_name + if len(parts) > 1: + template_name = parts[1] + for (template, info) in template_data: + if template == template_name: + template_info = _(info) + doc.SetTitle(_('Public Templates')) + break + else: + # Avoid cross-site scripting attacks + safetemplatename = Utils.websafe(template_name) + doc.SetTitle(_('Invalid Template')) + doc.AddItem(Div(Header(3, _('Invalid Template')), + Paragraph(_('%(safetemplatename)s is not a valid template.')) + ).Format(css='class="message error"')) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + return + else: + title = _('Public Templates') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_('Select template to edit:'))) + template_list = UnorderedList() + for (template, info) in template_data: + l = Link(mlist.GetScriptURL('edithtml') + '/' + template, _(info)) + template_list.AddItem(l) + doc.AddItem(template_list) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + return + + try: + if cgidata.keys(): + ChangeHTML(mlist, cgidata, template_name, doc) + FormatHTML(mlist, doc, template_name, template_info) + + finally: + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + + + +def FormatHTML(mlist, doc, template_name, template_info): + realname = mlist.real_name + title = _(template_info) + doc.AddItem(Header(1, title)) + + form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name) + text = Utils.maketext(template_name, raw=1, mlist=mlist) + # MAS: Don't websafe twice. TextArea does it. + form.AddItem(Paragraph(TextArea('html_code', text, rows=40, cols=75))) + form.AddItem(Paragraph( + _('When you are done making changes...'), + SubmitButton('submit', _('Submit Changes')).Format())) + doc.AddItem(form) + + + +def ChangeHTML(mlist, cgi_info, template_name, doc): + if not cgi_info.has_key('html_code'): + doc.AddItem(Div(Header(3, _('Template Unchanged.')), + Paragraph(_("Can't have empty template."))).Format(css='class="message error"')) + return + code = cgi_info['html_code'].value + code = re.sub(r'<([/]?script.*?)>', r'<\1>', code) + langdir = os.path.join(mlist.fullpath(), mlist.preferred_language) + + # Make sure the directory exists + omask = os.umask(0) + try: + try: + os.mkdir(langdir, 02775) + except OSError, e: + if e.errno <> errno.EEXIST: raise + finally: + os.umask(omask) + fp = open(os.path.join(langdir, template_name), 'w') + try: + fp.write(code) + finally: + fp.close() + + doc.AddItem(Div(Paragraph(_('Template successfully updated.'))).Format(css='class="message success strong"')) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/listinfo.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/listinfo.py new file mode 100755 index 0000000..346cd3c --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/listinfo.py @@ -0,0 +1,202 @@ +# Copyright (C) 1998-2003 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Produce listinfo page, primary web entry-point to mailing lists. +""" + +# No lock needed in this script, because we don't change data. + +import os +import cgi + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman import i18n +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + + +def main(): + parts = Utils.GetPathPieces() + if not parts: + listinfo_overview() + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + listinfo_overview(_('No such list %(safelistname)s')) + syslog('error', 'No such list "%s": %s', listname, e) + return + + # See if the user want to see this page in other language + cgidata = cgi.FieldStorage() + language = cgidata.getvalue('language') + if not Utils.IsLanguage(language): + language = mlist.preferred_language + i18n.set_language(language) + list_listinfo(mlist, language) + + +def listinfo_overview(msg=''): + # Present the general listinfo overview + hostname = Utils.get_domain() + # Set up the document and assign it the correct language. The only one we + # know about at the moment is the server's default. + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + legend = _('General Information') + ' - ' + _('Mailing Lists') + doc.SetTitle(legend) + + table = Table() + #table.AddRow([Center(Header(2, legend))]) + #table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, + # bgcolor=mm_cfg.WEB_HEADER_COLOR) + + # Skip any mailing lists that isn't advertised. + advertised = [] + listnames = Utils.list_names() + listnames.sort() + + for name in listnames: + mlist = MailList.MailList(name, lock=0) + if mlist.advertised: + if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ + mlist.web_page_url.find(hostname) == -1: + # List is for different identity of this host - skip it. + continue + else: + advertised.append((mlist.GetScriptURL('listinfo'), + mlist.real_name, + mlist.description)) + welcome = Header(1, _('General Information')).Format() + mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() + if not advertised: + welcome += Paragraph(_('There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.')).Format(css='class="strong"') + else: + welcome += Paragraph(_('''Below is a listing of all the public mailing lists on %(hostname)s. Click on a list name to get more information about the list, or to subscribe, unsubscribe, and change the preferences on your subscription.''')).Format() + + # set up some local variables + adj = msg and _('right') or '' + siteowner = Utils.get_site_email() + welcome += Paragraph( + _('''To visit the general information page for an unadvertised list, open a URL similar to this one, but with a '/' and the %(adj)s list name appended.''')).Format() + + welcome += Paragraph(_('''List administrators, you can visit ''') + + Link(Utils.ScriptURL('admin'), + _('the list admin overview page')).Format() + + _(''' to find the management interface for your list.''')).Format() + welcome += Paragraph(_('''If you are having trouble using the lists, please contact ''') + + Link('mailto:' + siteowner, siteowner).Format()).Format() + + if advertised: + highlight = 1 + for url, real_name, description in advertised: + table.AddRow( + [Link(url, real_name), + description or _('[no description available]')]) + if highlight: + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="title strong"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="title left"') + else: + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="strong"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="left"') + highlight = not highlight + + doc.AddItem(welcome) + # When printing the mailing list table; avoid empty
      tag to appear when + # no mailing list / rows are present inside it. Empty
      tags are a violation + # in the "-//W3C//DTD XHTML 1.0 Transitional//EN" standard. + if advertised: + doc.AddItem(table) + doc.AddItem(MailmanLogo()) + print doc.Format() + + + +def list_listinfo(mlist, lang): + # Generate list specific listinfo + doc = HeadlessDocument() + doc.set_language(lang) + + replacements = mlist.GetStandardReplacements(lang) + + if not mlist.digestable or not mlist.nondigestable: + replacements[''] = "" + replacements[''] = "" + replacements[''] = '' + else: + replacements[''] = mlist.FormatDigestButton() + replacements[''] = \ + mlist.FormatUndigestButton() + replacements[''] = '' + replacements[''] = '' + replacements[''] = \ + mlist.FormatPlainDigestsButton() + replacements[''] = mlist.FormatMimeDigestsButton() + replacements[''] = mlist.FormatBox('email', size=30) + replacements[''] = mlist.FormatButton( + 'email-button', text=_('Subscribe')) + replacements[''] = mlist.FormatSecureBox('pw') + replacements[''] = mlist.FormatSecureBox('pw-conf') + replacements[''] = mlist.FormatFormStart( + 'subscribe') + # Roster form substitutions + replacements[''] = mlist.FormatFormStart('roster') + replacements[''] = mlist.FormatRosterOptionForUser(lang) + # Options form substitutions + replacements[''] = mlist.FormatFormStart('options') + replacements[''] = mlist.FormatEditingOption(lang) + replacements[''] = SubmitButton('UserOptions', + _('Edit Options')).Format() + # If only one language is enabled for this mailing list, omit the choice + # buttons. + if len(mlist.GetAvailableLanguages()) == 1: + displang = '' + else: + displang = mlist.FormatButton('displang-button', + text = _('View this page in')) + replacements[''] = displang + replacements[''] = mlist.FormatFormStart('listinfo') + replacements[''] = mlist.FormatBox('fullname', size=30) + + # Links on header section (errormsg) + listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() + listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() + replacements[''] = listinfo_link + replacements[''] = listadmin_link + replacements[''] = _('Mailing Lists') + + # Do the expansion. + doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang)) + print doc.Format() + + + +if __name__ == "__main__": + main() diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/options.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/options.py new file mode 100755 index 0000000..5e99064 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/options.py @@ -0,0 +1,1023 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Produce and handle the member options.""" + +import sys +import os +import cgi +import signal +import urllib +from types import ListType + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman import MemberAdaptor +from Mailman import i18n +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +SLASH = '/' +SETLANGUAGE = -1 + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +def main(): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + parts = Utils.GetPathPieces() + lenparts = parts and len(parts) + if not parts or lenparts < 1: + title = _('CGI script error') + doc.SetTitle(title) + doc.addError(_('Invalid options to CGI script.')) + doc.AddItem(MailmanLogo()) + print doc.Format() + return + + # get the list and user's name + listname = parts[0].lower() + # open list + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + title = _('CGI script error') + doc.SetTitle(title) + doc.addError(_('No such list %(safelistname)s'), title) + doc.AddItem(MailmanLogo()) + print doc.Format() + syslog('error', 'No such list "%s": %s\n', listname, e) + return + + # The total contents of the user's response + cgidata = cgi.FieldStorage(keep_blank_values=1) + + # Set the language for the page. If we're coming from the listinfo cgi, + # we might have a 'language' key in the cgi data. That was an explicit + # preference to view the page in, so we should honor that here. If that's + # not available, use the list's default language. + language = cgidata.getvalue('language') + if not Utils.IsLanguage(language): + language = mlist.preferred_language + i18n.set_language(language) + doc.set_language(language) + + if lenparts < 2: + user = cgidata.getvalue('email') + if not user: + # If we're coming from the listinfo page and we left the email + # address field blank, it's not an error. listinfo.html names the + # button UserOptions; we can use that as the descriminator. + if not cgidata.getvalue('UserOptions'): + doc.addError(_('No address given')) + loginpage(mlist, doc, None, language) + print doc.Format() + return + else: + user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:]))) + + # Avoid cross-site scripting attacks + safeuser = Utils.websafe(user) + try: + Utils.ValidateEmail(user) + except Errors.EmailAddressError: + doc.addError(_('Illegal Email Address: %(safeuser)s')) + loginpage(mlist, doc, None, language) + print doc.Format() + return + # Sanity check the user, but only give the "no such member" error when + # using public rosters, otherwise, we'll leak membership information. + if not mlist.isMember(user) and mlist.private_roster == 0: + doc.addError(_('No such member: %(safeuser)s.')) + loginpage(mlist, doc, None, language) + print doc.Format() + return + + # Find the case preserved email address (the one the user subscribed with) + lcuser = user.lower() + try: + cpuser = mlist.getMemberCPAddress(lcuser) + except Errors.NotAMemberError: + # This happens if the user isn't a member but we've got private rosters + cpuser = None + if lcuser == cpuser: + cpuser = None + + # And now we know the user making the request, so set things up to for the + # user's stored preferred language, overridden by any form settings for + # their new language preference. + userlang = cgidata.getvalue('language') + if not Utils.IsLanguage(userlang): + userlang = mlist.getMemberLanguage(user) + doc.set_language(userlang) + i18n.set_language(userlang) + + # See if this is VARHELP on topics. + varhelp = None + if cgidata.has_key('VARHELP'): + varhelp = cgidata['VARHELP'].value + elif os.environ.get('QUERY_STRING'): + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') + if qs and type(qs) == types.ListType: + varhelp = qs[0] + if varhelp: + topic_details(mlist, doc, user, cpuser, userlang, varhelp) + return + + # Are we processing an unsubscription request from the login screen? + if cgidata.has_key('login-unsub'): + # Because they can't supply a password for unsubscribing, we'll need + # to do the confirmation dance. + if mlist.isMember(user): + # We must acquire the list lock in order to pend a request. + try: + mlist.Lock() + # If unsubs require admin approval, then this request has to + # be held. Otherwise, send a confirmation. + if mlist.unsubscribe_policy: + mlist.HoldUnsubscription(user) + doc.addMessage(_('Your unsubscription request has been forwarded to the list administrator for approval.'), + tag='', css='class="message warning strong"') + else: + mlist.ConfirmUnsubscription(user, userlang) + doc.addMessage(_('The confirmation email has been sent.'), + tag='', css='class="message warning strong"') + mlist.Save() + finally: + mlist.Unlock() + else: + # Not a member + if mlist.private_roster == 0: + # Public rosters + doc.addError(_('No such member: %(safeuser)s.')) + else: + syslog('mischief', + _('Unsub attempt of non-member w/ private rosters: %s'), + user) + doc.addMessage(_('The confirmation email has been sent.'), + tag='', css='class="message success strong"') + loginpage(mlist, doc, user, language) + print doc.Format() + return + + # Are we processing a password reminder from the login screen? + if cgidata.has_key('login-remind'): + if mlist.isMember(user): + mlist.MailUserPassword(user) + doc.addError( + _('A reminder of your password has been emailed to you.'), + tag='') + else: + # Not a member + if mlist.private_roster == 0: + # Public rosters + doc.addError(_('No such member: %(safeuser)s.')) + else: + syslog('mischief', + 'Reminder attempt of non-member w/ private rosters: %s', + user) + doc.addMessage( + _('A reminder of your password has been emailed to you.'), + tag='', css='class="message success strong"') + loginpage(mlist, doc, user, language) + print doc.Format() + return + + # Get the password from the form. + password = cgidata.getvalue('password', '').strip() + # Check authentication. We need to know if the credentials match the user + # or the site admin, because they are the only ones who are allowed to + # change things globally. Specifically, the list admin may not change + # values globally. + if mm_cfg.ALLOW_SITE_ADMIN_COOKIES: + user_or_siteadmin_context = (mm_cfg.AuthUser, mm_cfg.AuthSiteAdmin) + else: + # Site and list admins are treated equal so that list admin can pass + # site admin test. :-( + user_or_siteadmin_context = (mm_cfg.AuthUser,) + is_user_or_siteadmin = mlist.WebAuthenticate( + user_or_siteadmin_context, password, user) + # Authenticate, possibly using the password supplied in the login page + if not is_user_or_siteadmin and \ + not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + password, user): + # Not authenticated, so throw up the login page again. If they tried + # to authenticate via cgi (instead of cookie), then print an error + # message. + if cgidata.has_key('password'): + doc.addError(_('Authentication failed.')) + # So as not to allow membership leakage, prompt for the email + # address and the password here. + if mlist.private_roster <> 0: + syslog('mischief', + 'Login failure with private rosters: %s', + user) + user = None + loginpage(mlist, doc, user, language) + print doc.Format() + return + + # From here on out, the user is okay to view and modify their membership + # options. The first set of checks does not require the list to be + # locked. + + if cgidata.has_key('logout'): + print mlist.ZapCookie(mm_cfg.AuthUser, user) + loginpage(mlist, doc, user, language) + print doc.Format() + return + + if cgidata.has_key('emailpw'): + mlist.MailUserPassword(user) + msg = Div(Paragraph(_('A reminder of your password has been emailed to you.'))).Format(css='class="message success strong"') + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + + if cgidata.has_key('othersubs'): + # Only the user or site administrator can view all subscriptions. + if not is_user_or_siteadmin: + msg = Div(Paragraph(_('The list administrator may not view the other subscriptions for this user.'))).Format(css='class="message error strong"') + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + hostname = mlist.host_name + title = _('List of subscriptions for: %(safeuser)s') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + doc.AddItem(Paragraph(_('Click on a link to visit your options page for the requested mailing list.'))) + + # Troll through all the mailing lists that match host_name and see if + # the user is a member. If so, add it to the list. + onlists = [] + for gmlist in lists_of_member(mlist, user) + [mlist]: + url = gmlist.GetOptionsURL(user) + link = Link(url, gmlist.real_name) + onlists.append((gmlist.real_name, link)) + onlists.sort() + items = OrderedList(*[link for name, link in onlists]) + doc.AddItem(items) + doc.AddItem(MailmanLogo()) + print doc.Format() + return + + if cgidata.has_key('change-of-address'): + msg = '' + # We could be changing the user's full name, email address, or both. + # Watch out for non-ASCII characters in the member's name. + membername = cgidata.getvalue('fullname') + # Canonicalize the member's name + membername = Utils.canonstr(membername, language) + newaddr = cgidata.getvalue('new-address') + confirmaddr = cgidata.getvalue('confirm-address') + + oldname = mlist.getMemberName(user) + set_address = set_membername = 0 + + # See if the user wants to change their email address globally. The + # list admin is /not/ allowed to make global changes. + globally = cgidata.getvalue('changeaddr-globally') + if globally and not is_user_or_siteadmin: + msg += Div(Paragraph(_("The list administrator may not change the names or addresses for this user's other subscriptions. However, the subscription for this mailing list has been changed."))).Format(css='class="message warning strong"') + globally = False + # We will change the member's name under the following conditions: + # - membername has a value + # - membername has no value, but they /used/ to have a membername + if membername and membername <> oldname: + # Setting it to a new value + set_membername = 1 + if not membername and oldname: + # Unsetting it + set_membername = 1 + # We will change the user's address if both newaddr and confirmaddr + # are non-blank, have the same value, and aren't the currently + # subscribed email address (when compared case-sensitively). If both + # are blank, but membername is set, we ignore it, otherwise we print + # an error. + if newaddr and confirmaddr: + if newaddr <> confirmaddr: + msg += Div(Paragraph(_('Addresses did not match!'))).Format(css='class="message error strong"') + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + if newaddr == cpuser: + msg += Div(Paragraph(_('You are already using that email address'))).Format(css='class="message error strong"') + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + # If they're requesting to subscribe an address which is already a + # member, and they're /not/ doing it globally, then refuse. + # Otherwise, we'll agree to do it globally (with a warning + # message) and let ApprovedChangeMemberAddress() handle already a + # member issues. + if mlist.isMember(newaddr): + safenewaddr = Utils.websafe(newaddr) + if globally: + listname = mlist.real_name + msg += Div(Paragraph(_('The new address you requested %(newaddr)s is already a member of the %(listname)s mailing list, however you have also requested a global change of address. Upon confirmation, any other mailing list containing the address %(safeuser)s will be changed.'))).Format(css='class="message warning strong"') + # Don't return + else: + msg += Div(Paragraph( + _('The new address is already a member: %(newaddr)s') + )).Format(css='class="message error strong"') + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + set_address = 1 + elif (newaddr or confirmaddr) and not set_membername: + msg += Div(Paragraph(_('Addresses may not be blank'))).Format(css='class="message error strong"') + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + + # Standard sigterm handler. + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + signal.signal(signal.SIGTERM, sigterm_handler) + if set_address: + if cpuser is None: + cpuser = user + # Register the pending change after the list is locked + msg = Div(Paragraph(_('A confirmation message has been sent to: %(newaddr)s.'))).Format(css='class="message success strong"') + mlist.Lock() + try: + try: + mlist.ChangeMemberAddress(cpuser, newaddr, globally) + mlist.Save() + finally: + mlist.Unlock() + except Errors.MMBadEmailError: + msg = Div(Paragraph(_('Bad email address provided'))).Format(css='class="message error strong"') + except Errors.MMHostileAddress: + msg = Div(Paragraph(_('Illegal email address provided'))).Format(css='class="message error strong"') + except Errors.MMAlreadyAMember: + msg = Div(Paragraph(_('%(newaddr)s is already a member of the list.'))).Format(css='class="message error strong"') + except Errors.MembershipIsBanned: + owneraddr = mlist.GetOwnerEmail() + msg = Div(Paragraph(_('%(newaddr)s is banned from this list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.'))).Format(css='class="message warning strong"') + + if set_membername: + mlist.Lock() + try: + mlist.ChangeMemberName(user, membername, globally) + mlist.Save() + finally: + mlist.Unlock() + msg += Div(Paragraph(_('Member name successfully changed.'))).Format(css='class="message success strong"') + + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + + if cgidata.has_key('changepw'): + newpw = cgidata.getvalue('newpw') + confirmpw = cgidata.getvalue('confpw') + if not newpw or not confirmpw: + options_page(mlist, doc, user, cpuser, userlang, + Div(Paragraph(_('Passwords may not be blank'))).Format(css='class="message error strong"')) + print doc.Format() + return + if newpw <> confirmpw: + options_page(mlist, doc, user, cpuser, userlang, + Div(Paragraph(_('Passwords did not match!'))).Format(css='class="message error strong"')) + print doc.Format() + return + + # See if the user wants to change their passwords globally, however + # the list admin is /not/ allowed to change passwords globally. + msg = '' + pw_globally = cgidata.getvalue('pw-globally') + if pw_globally and not is_user_or_siteadmin: + msg += Div(Paragraph(_('''The list administrator may not change the password for this user's other subscriptions. However, the password for this mailing list has been changed.'''))).Format(css='class="message warning strong"') + pw_globally = False + + mlists = [mlist] + + if pw_globally: + mlists.extend(lists_of_member(mlist, user)) + + for gmlist in mlists: + change_password(gmlist, user, newpw, confirmpw) + + # Regenerate the cookie so a re-authorization isn't necessary + print mlist.MakeCookie(mm_cfg.AuthUser, user) + msg += Div(Paragraph(_('Password successfully changed.'))).Format(css='class="message success strong"') + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + + if cgidata.has_key('unsub'): + # Was the confirming check box turned on? + if not cgidata.getvalue('unsubconfirm'): + options_page( + mlist, doc, user, cpuser, userlang, + Div(Paragraph(_('''You must confirm your unsubscription request by turning on the checkbox below the Unsubscribe button. You have not been unsubscribed!'''))).Format(css='class="message error strong"')) + print doc.Format() + return + + # Standard signal handler + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + # Okay, zap them. Leave them sitting at the list's listinfo page. We + # must own the list lock, and we want to make sure the user (BAW: and + # list admin?) is informed of the removal. + signal.signal(signal.SIGTERM, sigterm_handler) + mlist.Lock() + needapproval = False + try: + try: + mlist.DeleteMember( + user, _('via the member options page'), userack=1) + except Errors.MMNeedApproval: + needapproval = True + mlist.Save() + finally: + mlist.Unlock() + # Now throw up some results page, with appropriate links. We can't + # drop them back into their options page, because that's gone now! + fqdn_listname = mlist.GetListEmail() + owneraddr = mlist.GetOwnerEmail() + url = mlist.GetScriptURL('listinfo', absolute=1) + + title = _('Unsubscription results') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + if needapproval: + doc.AddItem(Paragraph(_('Your unsubscription request has been received and forwarded on to the list moderators for approval. You will receive notification once the list moderators have made their decision.'))) + else: + doc.AddItem(Paragraph(_('You have been successfully unsubscribed from the mailing list %(fqdn_listname)s. If you were receiving digest deliveries you may get one more digest. If you have any questions about your unsubscription, please contact the list owners at %(owneraddr)s.'))) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + return + + if cgidata.has_key('options-submit'): + # Digest action flags + digestwarn = 0 + cantdigest = 0 + mustdigest = 0 + + newvals = [] + # First figure out which options have changed. The item names come + # from FormatOptionButton() in HTMLFormatter.py + for item, flag in (('digest', mm_cfg.Digests), + ('mime', mm_cfg.DisableMime), + ('dontreceive', mm_cfg.DontReceiveOwnPosts), + ('ackposts', mm_cfg.AcknowledgePosts), + ('disablemail', mm_cfg.DisableDelivery), + ('conceal', mm_cfg.ConcealSubscription), + ('remind', mm_cfg.SuppressPasswordReminder), + ('rcvtopic', mm_cfg.ReceiveNonmatchingTopics), + ('nodupes', mm_cfg.DontReceiveDuplicates), + ): + try: + newval = int(cgidata.getvalue(item)) + except (TypeError, ValueError): + newval = None + + # Skip this option if there was a problem or it wasn't changed. + # Note that delivery status is handled separate from the options + # flags. + if newval is None: + continue + elif flag == mm_cfg.DisableDelivery: + status = mlist.getDeliveryStatus(user) + # Here, newval == 0 means enable, newval == 1 means disable + if not newval and status <> MemberAdaptor.ENABLED: + newval = MemberAdaptor.ENABLED + elif newval and status == MemberAdaptor.ENABLED: + newval = MemberAdaptor.BYUSER + else: + continue + elif newval == mlist.getMemberOption(user, flag): + continue + # Should we warn about one more digest? + if flag == mm_cfg.Digests and \ + newval == 0 and mlist.getMemberOption(user, flag): + digestwarn = 1 + + newvals.append((flag, newval)) + + # The user language is handled a little differently + if userlang not in mlist.GetAvailableLanguages(): + newvals.append((SETLANGUAGE, mlist.preferred_language)) + else: + newvals.append((SETLANGUAGE, userlang)) + + # Process user selected topics, but don't make the changes to the + # MailList object; we must do that down below when the list is + # locked. + topicnames = cgidata.getvalue('usertopic') + if topicnames: + # Some topics were selected. topicnames can actually be a string + # or a list of strings depending on whether more than one topic + # was selected or not. + if not isinstance(topicnames, ListType): + # Assume it was a bare string, so listify it + topicnames = [topicnames] + # unquote the topic names + topicnames = [urllib.unquote_plus(n) for n in topicnames] + + # The standard sigterm handler (see above) + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + # Now, lock the list and perform the changes + mlist.Lock() + try: + signal.signal(signal.SIGTERM, sigterm_handler) + # `values' is a tuple of flags and the web values + for flag, newval in newvals: + # Handle language settings differently + if flag == SETLANGUAGE: + mlist.setMemberLanguage(user, newval) + # Handle delivery status separately + elif flag == mm_cfg.DisableDelivery: + mlist.setDeliveryStatus(user, newval) + else: + try: + mlist.setMemberOption(user, flag, newval) + except Errors.CantDigestError: + cantdigest = 1 + except Errors.MustDigestError: + mustdigest = 1 + # Set the topics information. + mlist.setMemberTopics(user, topicnames) + mlist.Save() + finally: + mlist.Unlock() + + # A bag of attributes for the global options + class Global: + enable = None + remind = None + nodupes = None + mime = None + def __nonzero__(self): + return len(self.__dict__.keys()) > 0 + + globalopts = Global() + + # The enable/disable option and the password remind option may have + # their global flags sets. + if cgidata.getvalue('deliver-globally'): + # Yes, this is inefficient, but the list is so small it shouldn't + # make much of a difference. + for flag, newval in newvals: + if flag == mm_cfg.DisableDelivery: + globalopts.enable = newval + break + + if cgidata.getvalue('remind-globally'): + for flag, newval in newvals: + if flag == mm_cfg.SuppressPasswordReminder: + globalopts.remind = newval + break + + if cgidata.getvalue('nodupes-globally'): + for flag, newval in newvals: + if flag == mm_cfg.DontReceiveDuplicates: + globalopts.nodupes = newval + break + + if cgidata.getvalue('mime-globally'): + for flag, newval in newvals: + if flag == mm_cfg.DisableMime: + globalopts.mime = newval + break + + # Change options globally, but only if this is the user or site admin, + # /not/ if this is the list admin. + if globalopts: + if not is_user_or_siteadmin: + doc.addMessage(_("""The list administrator may not change the options for this user's other subscriptions. However the options for this mailing list subscription has been changed."""), css='class="message warning strong"') + else: + for gmlist in lists_of_member(mlist, user): + global_options(gmlist, user, globalopts) + + # Now print the results + if cantdigest: + msg = _('''The list administrator has disabled digest delivery for this list, so your delivery option has not been set. However your other options have been set successfully.''') + elif mustdigest: + msg = _('''The list administrator has disabled non-digest delivery for this list, so your delivery option has not been set. However your other options have been set successfully.''') + else: + msg = _('You have successfully set your options.') + + if digestwarn: + msg += _(' You may get one last digest.') + + # Set message presentation + if cantdigest or mustdigest or digestwarn: + msg = Div(Paragraph(msg)).Format(css='class="message warning strong"') + else: + msg = Div(Paragraph(msg)).Format(css='class="message success strong"') + + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + + if mlist.isMember(user): + options_page(mlist, doc, user, cpuser, userlang) + else: + loginpage(mlist, doc, user, userlang) + print doc.Format() + + + +def options_page(mlist, doc, user, cpuser, userlang, message=''): + # The bulk of the document will come from the options.html template, which + # includes it's own html armor (head tags, etc.). Suppress the head that + # Document() derived pages get automatically. + doc.suppress_head = 1 + + if mlist.obscure_addresses: + presentable_user = Utils.ObscureEmail(user, for_text=1) + if cpuser is not None: + cpuser = Utils.ObscureEmail(cpuser, for_text=1) + else: + presentable_user = user + + fullname = Utils.uncanonstr(mlist.getMemberName(user), userlang) + if fullname: + presentable_user += ', %s' % Utils.websafe(fullname) + + # Do replacements + replacements = mlist.GetStandardReplacements(userlang) + if message: + # message should come formatted with + # Div(Paragraph()).Format(css='class="message success|error"') + replacements[''] = message + replacements[''] = mlist.FormatOptionButton( + mm_cfg.Digests, 1, user) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.Digests, 0, user) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.DisableMime, 1, user) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.DisableMime, 0, user) + replacements[''] = ( + CheckBox('mime-globally', 1, checked=0).Format()) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.DisableDelivery, 0, user) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.DisableDelivery, 1, user) + replacements[''] = mlist.FormatDisabledNotice(user) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.AcknowledgePosts, 0, user) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.AcknowledgePosts, 1, user) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.DontReceiveOwnPosts, 0, user) + replacements[''] = ( + mlist.FormatOptionButton(mm_cfg.DontReceiveOwnPosts, 1, user)) + replacements[''] = ( + mlist.FormatOptionButton(mm_cfg.SuppressPasswordReminder, 1, user)) + replacements[''] = ( + mlist.FormatOptionButton(mm_cfg.SuppressPasswordReminder, 0, user)) + replacements[''] = ( + mlist.FormatOptionButton(mm_cfg.ConcealSubscription, 0, user)) + replacements[''] = mlist.FormatOptionButton( + mm_cfg.ConcealSubscription, 1, user) + replacements[''] = ( + mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 1, user)) + replacements[''] = ( + mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 0, user)) + replacements[''] = ( + mlist.FormatButton('unsub', _('Unsubscribe')) + '

      ' + + CheckBox('unsubconfirm', 1, checked=0).Format() + + _('Yes, I really want to unsubscribe') + '

      ') + replacements[''] = mlist.FormatSecureBox('newpw') + replacements[''] = mlist.FormatSecureBox('confpw') + replacements[''] = ( + mlist.FormatButton('changepw', _("Change My Password"))) + replacements[''] = ( + mlist.FormatButton('othersubs', + _('List my other subscriptions'))) + replacements[''] = ( + mlist.FormatFormStart('options', user)) + replacements[''] = user + replacements[''] = presentable_user + replacements[''] = mlist.FormatButton( + 'emailpw', (_('Email My Password To Me'))) + replacements[''] = ( + mlist.FormatUmbrellaNotice(user, _("password"))) + replacements[''] = ( + mlist.FormatButton('logout', _('Log out'))) + replacements[''] = mlist.FormatButton( + 'options-submit', _('Submit My Changes')) + replacements[''] = ( + CheckBox('pw-globally', 1, checked=0).Format()) + replacements[''] = ( + CheckBox('deliver-globally', 1, checked=0).Format()) + replacements[''] = ( + CheckBox('remind-globally', 1, checked=0).Format()) + replacements[''] = ( + CheckBox('nodupes-globally', 1, checked=0).Format()) + + days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1)) + if days > 1: + units = _('days') + else: + units = _('day') + replacements[''] = _('%(days)d %(units)s') + + replacements[''] = mlist.FormatBox('new-address') + replacements[''] = mlist.FormatBox( + 'confirm-address') + replacements[''] = mlist.FormatButton( + 'change-of-address', _('Change My Address and Name')) + replacements[''] = CheckBox( + 'changeaddr-globally', 1, checked=0).Format() + replacements[''] = mlist.FormatBox( + 'fullname', value=fullname) + + # Create the topics radios. BAW: what if the list admin deletes a topic, + # but the user still wants to get that topic message? + usertopics = mlist.getMemberTopics(user) + if mlist.topics: + table = Table(border="0") + for name, pattern, description, emptyflag in mlist.topics: + quotedname = urllib.quote_plus(name) + details = Link(mlist.GetScriptURL('options') + + '/%s/?VARHELP=%s' % (user, quotedname), + ' (Details)') + if name in usertopics: + checked = 1 + else: + checked = 0 + table.AddRow([CheckBox('usertopic', quotedname, checked=checked), + name + details.Format()]) + topicsfield = table.Format() + else: + topicsfield = _('No topics defined') + replacements[''] = topicsfield + replacements[''] = ( + mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 0, user)) + replacements[''] = ( + mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 1, user)) + + if cpuser is not None: + replacements[''] = Div( + Paragraph(_('''You are subscribed to this list with the case-preserved address: %(cpuser)s.''') + )).Format(css='class="message warning strong"') + else: + replacements[''] = '' + + # Links on header section (errormsg) + # Beacause this is on a html template file replacements is need in + # order to make the exapantion of these links. + listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() + listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() + replacements[''] = listinfo_link + replacements[''] = listadmin_link + replacements[''] = _('Mailing Lists') + + doc.AddItem(mlist.ParseTags('options.html', replacements, userlang)) + + + +def loginpage(mlist, doc, user, lang): + realname = mlist.real_name + actionurl = mlist.GetScriptURL('options') + + # Set up the title. + title = _('%(realname)s: membership options') + doc.SetTitle(title) + doc.AddItem(Header(1, title)) + + # Set up what this page is for + if user is None: + title = _('Login page') + extra = _('email address and ') + else: + safeuser = Utils.websafe(user) + title = _('for user: ') + safeuser + obuser = Utils.ObscureEmail(user) + extra = '' + + doc.AddItem(Paragraph(title).Format(0, 'class="strong"')) + + # Language selecton box + # If only one language is enabled for this mailing list, omit the choice + # buttons. + if len(mlist.GetAvailableLanguages()) > 1: + langform = Form(actionurl) + langform.AddItem(Paragraph(SubmitButton('displang-button', + _('View this page in')).Format(), + mlist.GetLangSelectBox(lang)).Format()) + if user: + langform.AddItem(Paragraph(Hidden('email', user))) + doc.AddItem(langform) + + # Preamble + # Set up the login page + form = Form(actionurl) + table = Table() + doc.AddItem(Paragraph(_("""In order to change your membership option, you must first log in by giving your %(extra)s membership password in the section below. If you don't remember your membership password, you can have it emailed to you by clicking on the button below. If you just want to unsubscribe from this list, click on the Unsubscribe button and a confirmation message will be sent to you."""))) + + doc.AddItem(Paragraph(_('''Important: From this point on, you must have cookies enabled in your browser, otherwise none of your changes will take effect.'''))) + + # Password and login button + table.AddRow([Header(3, _('Login'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') + if user is None: + table.AddRow([_('Email address:'), + TextBox('email', size=20)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + else: + table.AddRow([Hidden('email', user)]) + table.AddRow([_('Password'), PasswordBox('password', size=20)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + table.AddRow(['', SubmitButton('login', _('Log in'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + # Unsubscribe section + table.AddRow([Header(3, _('Unsubscribe'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') + + table.AddRow([_("""By clicking on the Unsubscribe button, a confirmation message will be emailed to you. This message will have a link that you should click on to complete the removal process (you can also confirm by email; see the instructions in the confirmation message)."""), + SubmitButton('login-unsub', _('Unsubscribe'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + # Password reminder section + table.AddRow([Header(3, _('Password reminder'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') + + table.AddRow([_("""By clicking on the Remind button, your password will be emailed to you."""), + SubmitButton('login-remind', _('Remind'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + # Finish up glomming together the login page + form.AddItem(table) + doc.AddItem(form) + doc.AddItem(mlist.GetMailmanFooter()) + + + +def lists_of_member(mlist, user): + hostname = mlist.host_name + onlists = [] + for listname in Utils.list_names(): + # The current list will always handle things in the mainline + if listname == mlist.internal_name(): + continue + glist = MailList.MailList(listname, lock=0) + if glist.host_name <> hostname: + continue + if not glist.isMember(user): + continue + onlists.append(glist) + return onlists + + + +def change_password(mlist, user, newpw, confirmpw): + # This operation requires the list lock, so let's set up the signal + # handling so the list lock will get released when the user hits the + # browser stop button. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + # Must own the list lock! + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + # change the user's password. The password must already have been + # compared to the confirmpw and otherwise been vetted for + # acceptability. + mlist.setMemberPassword(user, newpw) + mlist.Save() + finally: + mlist.Unlock() + + + +def global_options(mlist, user, globalopts): + # Is there anything to do? + for attr in dir(globalopts): + if attr.startswith('_'): + continue + if getattr(globalopts, attr) is not None: + break + else: + return + + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + # Must own the list lock! + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + + if globalopts.enable is not None: + mlist.setDeliveryStatus(user, globalopts.enable) + + if globalopts.remind is not None: + mlist.setMemberOption(user, mm_cfg.SuppressPasswordReminder, + globalopts.remind) + + if globalopts.nodupes is not None: + mlist.setMemberOption(user, mm_cfg.DontReceiveDuplicates, + globalopts.nodupes) + + if globalopts.mime is not None: + mlist.setMemberOption(user, mm_cfg.DisableMime, globalopts.mime) + + mlist.Save() + finally: + mlist.Unlock() + + + +def topic_details(mlist, doc, user, cpuser, userlang, varhelp): + # Find out which topic the user wants to get details of + reflist = varhelp.split('/') + name = None + topicname = _('') + if len(reflist) == 1: + topicname = urllib.unquote_plus(reflist[0]) + for name, pattern, description, emptyflag in mlist.topics: + if name == topicname: + break + else: + name = None + + if not name: + options_page(mlist, doc, user, cpuser, userlang, + _('Requested topic is not valid: %(topicname)s')) + print doc.Format() + return + + table = Table() + table.AddRow([Header(3,_('Topic filter details'))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') + table.AddRow([_('Name:'), + Utils.websafe(name)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + table.AddRow([_('Pattern (as regexp):'), + '' + Utils.websafe(pattern) + '']) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + table.AddRow([_('Description:'), + Utils.websafe(description)]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + options_page(mlist, doc, user, cpuser, userlang, table.Format()) + print doc.Format() diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/private.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/private.py new file mode 100755 index 0000000..e85a01d --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/private.py @@ -0,0 +1,195 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Provide a password-interface wrapper around private archives.""" + +import os +import sys +import cgi +import mimetypes + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman import i18n +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +# Set up i18n. Until we know which list is being requested, we use the +# server's default. +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + +SLASH = '/' + + + +def true_path(path): + "Ensure that the path is safe by removing .." + # Workaround for path traverse vulnerability. Unsuccessful attempts will + # be logged in logs/error. + parts = [x for x in path.split(SLASH) if x not in ('.', '..')] + return SLASH.join(parts)[1:] + + + +def guess_type(url, strict): + if hasattr(mimetypes, 'common_types'): + return mimetypes.guess_type(url, strict) + return mimetypes.guess_type(url) + + + +def main(): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + parts = Utils.GetPathPieces() + if not parts: + title = 'Private Archive Error' + doc.SetTitle(title) + doc.addError(_('You must specify a list.'), title) + doc.AddItem(MailmanLogo()) + print doc.Format() + return + + path = os.environ.get('PATH_INFO') + tpath = true_path(path) + if tpath <> path[1:]: + title = _('Private archive - "./" and "../" not allowed in URL.') + doc.SetTitle(title) + doc.addError(title) + doc.AddItem(MailmanLogo()) + print doc.Format() + syslog('mischief', 'Private archive hostile path: %s', path) + return + # BAW: This needs to be converted to the Site module abstraction + true_filename = os.path.join( + mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) + + listname = parts[0].lower() + mboxfile = '' + if len(parts) > 1: + mboxfile = parts[1] + + # See if it's the list's mbox file is being requested + if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \ + listname[:-5] == mboxfile[:-5]: + listname = listname[:-5] + else: + mboxfile = '' + + # If it's a directory, we have to append index.html in this script. We + # must also check for a gzipped file, because the text archives are + # usually stored in compressed form. + if os.path.isdir(true_filename): + true_filename = true_filename + '/index.html' + if not os.path.exists(true_filename) and \ + os.path.exists(true_filename + '.gz'): + true_filename = true_filename + '.gz' + + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + title = _('Private Archive Error') + doc.SetTitle(title) + doc.addError(_('No such list %(safelistname)s'), title) + doc.AddItem(MailmanLogo()) + print doc.Format() + syslog('error', 'No such list "%s": %s\n', listname, e) + return + + i18n.set_language(mlist.preferred_language) + doc.set_language(mlist.preferred_language) + + cgidata = cgi.FieldStorage() + username = cgidata.getvalue('username', '') + password = cgidata.getvalue('password', '') + + is_auth = 0 + realname = mlist.real_name + message = '' + + if not mlist.WebAuthenticate((mm_cfg.AuthUser, + mm_cfg.AuthListModerator, + mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + password, username): + if cgidata.has_key('submit'): + # This is a re-authorization attempt + message = Div(Paragraph(_('Authorization failed.'))).Format(css='class="message error strong"') + # Output the password form + charset = Utils.GetCharSet(mlist.preferred_language) + print 'Content-type: text/html; charset=' + charset + '\n\n' + # Put the original full path in the authorization form, but avoid + # trailing slash if we're not adding parts. We add it below. + action = mlist.GetScriptURL('private', absolute=1) + if parts[1:]: + action = os.path.join(action, SLASH.join(parts[1:])) + # If we added '/index.html' to true_filename, add a slash to the URL. + # We need this because we no longer add the trailing slash in the + # private.html template. It's always OK to test parts[-1] since we've + # already verified parts[0] is listname. The basic rule is if the + # post URL (action) is a directory, it must be slash terminated, but + # not if it's a file. Otherwise, relative links in the target archive + # page don't work. + if true_filename.endswith('/index.html') and parts[-1] <> 'index.html': + action += SLASH + # Escape web input parameter to avoid cross-site scripting. + print Utils.maketext( + 'private.html', + {'action' : Utils.websafe(action), + 'realname': mlist.real_name, + 'message' : message, + 'errormsg_header' : _('Mailing Lists'), + 'listinfo_link' : Link(Utils.ScriptURL('listinfo'), _('General Information')).Format(), + 'listadmin_link' : Link(Utils.ScriptURL('admin'), _('Administration')).Format(), + }, mlist=mlist) + return + + lang = mlist.getMemberLanguage(username) + i18n.set_language(lang) + doc.set_language(lang) + + # Authorization confirmed... output the desired file + try: + ctype, enc = guess_type(path, strict=0) + if ctype is None: + ctype = 'text/html' + if mboxfile: + f = open(os.path.join(mlist.archive_dir() + '.mbox', + mlist.internal_name() + '.mbox')) + ctype = 'text/plain' + elif true_filename.endswith('.gz'): + import gzip + f = gzip.open(true_filename, 'r') + else: + f = open(true_filename, 'r') + except IOError: + title = _('Private Archive Error') + doc.SetTitle(title) + doc.addError(_('Private archive file not found'), title) + doc.addItem(MailmanLogo()) + print doc.Format() + syslog('error', 'Private archive file not found: %s', true_filename) + else: + print 'Content-type: %s\n' % ctype + sys.stdout.write(f.read()) + f.close() diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/rmlist.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/rmlist.py new file mode 100755 index 0000000..415dff7 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/rmlist.py @@ -0,0 +1,228 @@ +# Copyright (C) 2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Remove/delete mailing lists through the web. +""" + +import os +import cgi +import sys +import errno +import shutil + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman import i18n +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + + +def main(): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + cgidata = cgi.FieldStorage() + parts = Utils.GetPathPieces() + + if not parts: + # Bad URL specification + title = _('Bad URL specification') + doc.SetTitle(title) + doc.addMessage('The specified URL is not valid.', title, 'class="message error"') + doc.AddItem(MailmanLogo()) + print doc.Format() + syslog('error', 'Bad URL specification: %s', parts) + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + title = _('Nos such list') + doc.SetTitle(title) + doc.addMessage( + _('The %(safelistname)s mailing list does not exist.'), + title, + css='class="message error"') + doc.AddItem(MailmanLogo()) + print doc.Format() + syslog('error', 'No such list "%s": %s\n', listname, e) + return + + # Now that we have a valid mailing list, set the language + i18n.set_language(mlist.preferred_language) + doc.set_language(mlist.preferred_language) + + # Be sure the list owners are not sneaking around! + if not mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS: + title = _("You're being a sneaky list owner!") + doc.SetTitle(title) + doc.addMessage(title, css='class="message error strong"') + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + syslog('mischief', 'Attempt to sneakily delete a list: %s', listname) + return + + if cgidata.has_key('doit'): + process_request(doc, cgidata, mlist) + print doc.Format() + return + + request_deletion(doc, mlist) + + # Always add the footer and print the document + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + + + +def process_request(doc, cgidata, mlist): + password = cgidata.getvalue('password', '').strip() + try: + delarchives = int(cgidata.getvalue('delarchives', '0')) + except ValueError: + delarchives = 0 + + # Removing a list is limited to the list-creator (a.k.a. list-destroyer), + # the list-admin, or the site-admin. Don't use WebAuthenticate here + # because we want to be sure the actual typed password is valid, not some + # password sitting in a cookie. + if mlist.Authenticate((mm_cfg.AuthCreator, + mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + password) == mm_cfg.UnAuthorized: + request_deletion( + doc, mlist, + _('You are not authorized to delete this mailing list')) + # Add the footer to properly close the tags. + doc.AddItem(mlist.GetMailmanFooter()) + return + + # Do the MTA-specific list deletion tasks + if mm_cfg.MTA: + modname = 'Mailman.MTA.' + mm_cfg.MTA + __import__(modname) + sys.modules[modname].remove(mlist, cgi=1) + + REMOVABLES = ['lists/%s'] + + if delarchives: + REMOVABLES.extend(['archives/private/%s', + 'archives/private/%s.mbox', + 'archives/public/%s', + 'archives/public/%s.mbox', + ]) + + problems = 0 + listname = mlist.internal_name() + for dirtmpl in REMOVABLES: + dir = os.path.join(mm_cfg.VAR_PREFIX, dirtmpl % listname) + if os.path.islink(dir): + try: + os.unlink(dir) + except OSError, e: + if e.errno not in (errno.EACCES, errno.EPERM): raise + problems += 1 + syslog('error', + 'link %s not deleted due to permission problems', + dir) + elif os.path.isdir(dir): + try: + shutil.rmtree(dir) + except OSError, e: + if e.errno not in (errno.EACCES, errno.EPERM): raise + problems += 1 + syslog('error', + 'directory %s not deleted due to permission problems', + dir) + + title = _('Mailing list deletion results') + doc.SetTitle(title) + container = Container() + #container.AddItem(Header(1, title)) + if not problems: + container.addMessage(_('''You have successfully deleted the mailing list %(listname)s.'''), + css='class="message success strong"') + else: + sitelist = Utils.get_site_email(mlist.host_name) + container.AddItem(Paragraph(_('''There were some problems deleting the mailing list %(listname)s. Contact your site administrator at %(sitelist)s for details.'''))) + doc.AddItem(container) + doc.AddItem(Paragraph( + _('Return to the ') + + Link(Utils.ScriptURL('listinfo'), + _('general list overview')).Format() + + '
      ' + _('Return to the ') + + Link(Utils.ScriptURL('admin'), + _('administrative list overview')).Format())) + doc.AddItem(MailmanLogo()) + + + +def request_deletion(doc, mlist, errmsg=None): + realname = mlist.real_name + title = _('Permanently remove mailing list %(realname)s') + doc.SetTitle(title) + + container = Container() + + # Add any error message as first element in the page + if errmsg: + container.addMessage(errmsg, css='class="message error strong"') + + # Add header as second element in the page + container.AddItem(Header(1, title)) + + container.AddItem(Paragraph(_("""This page allows you as the list owner, to permanent remove this mailing list from the system. This action is not undoable so you should undertake it only if you are absolutely sure this mailing list has served its purpose and is no longer necessary."""))) + + container.AddItem(Paragraph(_('''Note that no warning will be sent to your list members and after this action, any subsequent messages sent to the mailing list, or any of its administrative addreses will bounce.'''))) + + container.AddItem(Paragraph(_('''You also have the option of removing the archives for this mailing list at this time. It is almost always recommended that you do not remove the archives, since they serve as the historical record of your mailing list.'''))) + + container.AddItem(Paragraph(_('''For your safety, you will be asked to reconfirm the list password.'''))) + + form = Form(mlist.GetScriptURL('rmlist')) + ftable = Table() + + ftable.AddRow([_('List password:'), PasswordBox('password')]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, css='class="description"') + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, css='class="value"') + + ftable.AddRow([_('Also delete archives?'), + RadioButtonArray('delarchives', (_('No'), _('Yes')), + checked=0, values=(0, 1))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, css='class="description"') + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, css='class="value"') + + ftable.AddRow([Link( + mlist.GetScriptURL('admin'), + _('Cancel and return to list administration')).Format()]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, css='class="center"') + + ftable.AddRow([SubmitButton('doit', _('Delete this list'))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') + + form.AddItem(ftable) + container.AddItem(form) + doc.AddItem(container) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/roster.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/roster.py new file mode 100755 index 0000000..37937cc --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/roster.py @@ -0,0 +1,136 @@ +# Copyright (C) 1998-2003 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Produce subscriber roster, using listinfo form data, roster.html template. + +Takes listname in PATH_INFO. +""" + + +# We don't need to lock in this script, because we're never going to change +# data. + +import sys +import os +import cgi +import urllib + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman import i18n +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + + +def main(): + parts = Utils.GetPathPieces() + if not parts: + error_page(_('Invalid options to CGI script')) + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + error_page(_('No such list %(safelistname)s')) + syslog('error', 'roster: no such list "%s": %s', listname, e) + return + + cgidata = cgi.FieldStorage() + + # messages in form should go in selected language (if any...) + lang = cgidata.getvalue('language') + if not Utils.IsLanguage(lang): + lang = mlist.preferred_language + i18n.set_language(lang) + + # Perform authentication for protected rosters. If the roster isn't + # protected, then anybody can see the pages. If members-only or + # "admin"-only, then we try to cookie authenticate the user, and failing + # that, we check roster-email and roster-pw fields for a valid password. + # (also allowed: the list moderator, the list admin, and the site admin). + if mlist.private_roster == 0: + # No privacy + ok = 1 + elif mlist.private_roster == 1: + # Members only + addr = cgidata.getvalue('roster-email', '') + password = cgidata.getvalue('roster-pw', '') + ok = mlist.WebAuthenticate((mm_cfg.AuthUser, + mm_cfg.AuthListModerator, + mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + password, addr) + else: + # Admin only, so we can ignore the address field + password = cgidata.getvalue('roster-pw', '') + ok = mlist.WebAuthenticate((mm_cfg.AuthListModerator, + mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + password) + if not ok: + realname = mlist.real_name + doc = Document() + doc.set_language(lang) + error_page_doc(doc, _('%(realname)s roster authentication failed.')) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + return + + # The document and its language + doc = HeadlessDocument() + doc.set_language(lang) + + replacements = mlist.GetAllReplacements(lang) + replacements[''] = mlist.FormatButton( + 'displang-button', + text = _('View this page in')) + replacements[''] = mlist.FormatFormStart('roster') + + # Links on header section (errormsg) + listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() + listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() + replacements[''] = listinfo_link + replacements[''] = listadmin_link + replacements[''] = _('Mailing Lists') + + doc.AddItem(mlist.ParseTags('roster.html', replacements, lang)) + print doc.Format() + + + +def error_page(errmsg): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + error_page_doc(doc, errmsg) + doc.AddItem(MailmanLogo()) + print doc.Format() + + +def error_page_doc(doc, errmsg, *args): + # Produce a simple error-message page on stdout and exit. + title = _('Error') + doc.SetTitle(title) + doc.addMessage(errmsg % args, title, css='class="message error"') diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/subscribe.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/subscribe.py new file mode 100755 index 0000000..a69df34 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Cgi/subscribe.py @@ -0,0 +1,270 @@ +# Copyright (C) 1998-2003 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Process subscription or roster requests from listinfo form.""" + +import sys +import os +import cgi +import signal + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman import i18n +from Mailman import Message +from Mailman.UserDesc import UserDesc +from Mailman.htmlformat import * +from Mailman.Logging.Syslog import syslog + +SLASH = '/' +ERRORSEP = '\n\n

      ' + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + + +def main(): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + parts = Utils.GetPathPieces() + if not parts: + title = 'Error' + doc.SetTitle(title) + doc.addMessage(_('Invalid options to CGI script'), title, 'class="message error"') + doc.AddItem(MailmanLogo()) + print doc.Format() + return + + listname = parts[0].lower() + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + title = _('No such list') + doc.SetTitle(title) + doc.addMessage(_('The %(safelistname)s mailing list does not exist.'), + title, 'class="message error"') + doc.AddItem(MailmanLogo()) + print doc.Format() + syslog('error', 'No such list "%s": %s\n', listname, e) + return + + # See if the form data has a preferred language set, in which case, use it + # for the results. If not, use the list's preferred language. + cgidata = cgi.FieldStorage() + language = cgidata.getvalue('language') + if not Utils.IsLanguage(language): + language = mlist.preferred_language + i18n.set_language(language) + doc.set_language(language) + + # We need a signal handler to catch the SIGTERM that can come from Apache + # when the user hits the browser's STOP button. See the comment in + # admin.py for details. + # + # BAW: Strictly speaking, the list should not need to be locked just to + # read the request database. However the request database asserts that + # the list is locked in order to load it and it's not worth complicating + # that logic. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + + process_form(mlist, doc, cgidata, language) + mlist.Save() + finally: + mlist.Unlock() + + + +def process_form(mlist, doc, cgidata, lang): + listowner = mlist.GetOwnerEmail() + realname = mlist.real_name + results = [] + + # The email address being subscribed, required + email = cgidata.getvalue('email', '') + if not email: + results.append(Paragraph(_('You must supply a valid email address.')).Format()) + + fullname = cgidata.getvalue('fullname', '') + # Canonicalize the full name + fullname = Utils.canonstr(fullname, lang) + # Who was doing the subscribing? + remote = os.environ.get('REMOTE_HOST', + os.environ.get('REMOTE_ADDR', + 'unidentified origin')) + # Was an attempt made to subscribe the list to itself? + if email == mlist.GetListEmail(): + syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote) + results.append(Paragraph(_('You may not subscribe a list to itself!')).Format()) + # If the user did not supply a password, generate one for him + password = cgidata.getvalue('pw') + confirmed = cgidata.getvalue('pw-conf') + + if password is None and confirmed is None: + password = Utils.MakeRandomPassword() + elif password is None or confirmed is None: + results.append(Paragraph(_('If you supply a password, you must confirm it.')).Format()) + elif password <> confirmed: + results.append(Paragraph(_('Your passwords did not match.')).Format()) + + # Get the digest option for the subscription. + digestflag = cgidata.getvalue('digest') + if digestflag: + try: + digest = int(digestflag) + except ValueError: + digest = 0 + else: + digest = mlist.digest_is_default + + # Sanity check based on list configuration. BAW: It's actually bogus that + # the page allows you to set the digest flag if you don't really get the + # choice. :/ + if not mlist.digestable: + digest = 0 + elif not mlist.nondigestable: + digest = 1 + + if results: + print_results(mlist, ERRORSEP.join(results), doc, lang) + return + + # If this list has private rosters, we have to be careful about the + # message that gets printed, otherwise the subscription process can be + # used to mine for list members. It may be inefficient, but it's still + # possible, and that kind of defeats the purpose of private rosters. + # We'll use this string for all successful or unsuccessful subscription + # results. + if mlist.private_roster == 0: + # Public rosters + privacy_results = '' + else: + privacy_results = Paragraph(_("""Your subscription request has been received, and will soon be acted upon. Depending on the configuration of this mailing list, your subscription request may have to be first confirmed by you via email, or approved by the list moderator. If confirmation is required, you will soon get a confirmation email which contains further instructions.""")).Format() + + try: + userdesc = UserDesc(email, fullname, password, digest, lang) + mlist.AddMember(userdesc, remote) + results = '' + # Check for all the errors that mlist.AddMember can throw options on the + # web page for this cgi + except Errors.MembershipIsBanned: + results = Paragraph(_("""The email address you supplied is banned from this mailing list. If you think this restriction is erroneous, please contact the list owners at %(listowner)s.""")).Format() + except Errors.MMBadEmailError: + results = Paragraph(_("""The email address you supplied is not valid. (E.g. it must contain an `@'.)""")).Format() + except Errors.MMHostileAddress: + results = Paragraph(_('Your subscription is not allowed because the email address you gave is insecure.')).Format() + except Errors.MMSubscribeNeedsConfirmation: + # Results string depends on whether we have private rosters or not + if privacy_results: + results = privacy_results + else: + results = Paragraph(_("""Confirmation from your email address is required, to prevent anyone from subscribing you without permission. Instructions are being sent to you at %(email)s. Please note your subscription will not start until you confirm your subscription.""")).Format() + except Errors.MMNeedApproval, x: + # Results string depends on whether we have private rosters or not + if privacy_results: + results = privacy_results + else: + # We need to interpolate into x + x = _(x) + results = Paragraph(_("""Your subscription request was deferred because %(x)s. Your request has been forwarded to the list moderator. You will receive email informing you of the moderator's decision when they get to your request.""")).Format() + except Errors.MMAlreadyAMember: + # Results string depends on whether we have private rosters or not + if not privacy_results: + results = Paragraph(_('You are already subscribed.')).Format() + else: + results = privacy_results + # This could be a membership probe. For safety, let the user know + # a probe occurred. BAW: should we inform the list moderator? + listaddr = mlist.GetListEmail() + # Set the language for this email message to the member's language. + mlang = mlist.getMemberLanguage(email) + otrans = i18n.get_translation() + i18n.set_language(mlang) + try: + msg = Message.UserNotification( + mlist.getMemberCPAddress(email), + mlist.GetBouncesEmail(), + _('Mailman privacy alert'), + _("""\ +An attempt was made to subscribe your address to the mailing list +%(listaddr)s. You are already subscribed to this mailing list. + +Note that the list membership is not public, so it is possible that a bad +person was trying to probe the list for its membership. This would be a +privacy violation if we let them do this, but we didn't. + +If you submitted the subscription request and forgot that you were already +subscribed to the list, then you can ignore this message. If you suspect that +an attempt is being made to covertly discover whether you are a member of this +list, and you are worried about your privacy, then feel free to send a message +to the list administrator at %(listowner)s. +"""), lang=mlang) + finally: + i18n.set_translation(otrans) + msg.send(mlist) + # These shouldn't happen unless someone's tampering with the form + except Errors.MMCantDigestError: + results = Paragraph(_('This list does not support digest delivery.')).Format() + except Errors.MMMustDigestError: + results = Paragraph(_('This list only supports digest delivery.')).Format() + else: + # Everything's cool. Our return string actually depends on whether + # this list has private rosters or not + if privacy_results: + results = privacy_results + else: + results = Paragarph(_("""You have been successfully subscribed to the %(realname)s mailing list.""")).Format() + # Show the results + print_results(mlist, results, doc, lang) + + + +def print_results(mlist, results, doc, lang): + # The bulk of the document will come from the options.html template, which + # includes its own html armor (head tags, etc.). Suppress the head that + # Document() derived pages get automatically. + doc.suppress_head = 1 + + replacements = mlist.GetStandardReplacements(lang) + replacements[''] = results + + # Links on header section (errormsg) + listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() + listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() + replacements[''] = listinfo_link + replacements[''] = listadmin_link + replacements[''] = _('Mailing Lists') + + output = mlist.ParseTags('subscribe.html', replacements, lang) + doc.AddItem(output) + print doc.Format() diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Defaults.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Defaults.py new file mode 100644 index 0000000..34a56b6 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Defaults.py @@ -0,0 +1,1372 @@ +# -*- python -*- + +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Distributed default settings for significant Mailman config variables.""" + +# NEVER make site configuration changes to this file. ALWAYS make them in +# mm_cfg.py instead, in the designated area. See the comments in that file +# for details. + + +import os + +def seconds(s): return s +def minutes(m): return m * 60 +def hours(h): return h * 60 * 60 +def days(d): return d * 60 * 60 * 24 + +# Some convenient constants +try: + True, False +except NameError: + True = 1 + False = 0 + +Yes = yes = On = on = True +No = no = Off = off = False + + + +##### +# General system-wide defaults +##### + +# Should image logos be used? Set this to 0 to disable image logos from "our +# sponsors" and just use textual links instead (this will also disable the +# shortcut "favicon"). Otherwise, this should contain the URL base path to +# the logo images (and must contain the trailing slash).. If you want to +# disable Mailman's logo footer altogther, hack +# Mailman/htmlformat.py:MailmanLogo(), which also contains the hardcoded links +# and image names. +IMAGE_LOGOS = '/icons/' + +# The name of the Mailman favicon +SHORTCUT_ICON = 'mm-icon.png' + +# Don't change MAILMAN_URL, unless you want to point it at one of the mirrors. +MAILMAN_URL = 'http://www.gnu.org/software/mailman/index.html' +#MAILMAN_URL = 'http://www.list.org/' +#MAILMAN_URL = 'http://mailman.sf.net/' + +# Mailman needs to know about (at least) two fully-qualified domain names +# (fqdn); 1) the hostname used in your urls, and 2) the hostname used in email +# addresses for your domain. For example, if people visit your Mailman system +# with "http://www.dom.ain/mailman" then your url fqdn is "www.dom.ain", and +# if people send mail to your system via "yourlist@dom.ain" then your email +# fqdn is "dom.ain". DEFAULT_URL_HOST controls the former, and +# DEFAULT_EMAIL_HOST controls the latter. Mailman also needs to know how to +# map from one to the other (this is especially important if you're running +# with virtual domains). You use "add_virtualhost(urlfqdn, emailfqdn)" to add +# new mappings. +# +# If you don't need to change DEFAULT_EMAIL_HOST and DEFAULT_URL_HOST in your +# mm_cfg.py, then you're done; the default mapping is added automatically. If +# however you change either variable in your mm_cfg.py, then be sure to also +# include the following: +# +# add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST) +# +# because otherwise the default mappings won't be correct. +DEFAULT_EMAIL_HOST = 'localhost.localdomain' +DEFAULT_URL_HOST = 'localhost.localdomain' +DEFAULT_URL_PATTERN = 'http://%s/mailman/' + +# DEFAULT_HOST_NAME has been replaced with DEFAULT_EMAIL_HOST, however some +# sites may have the former in their mm_cfg.py files. If so, we'll believe +# that, otherwise we'll believe DEFAULT_EMAIL_HOST. Same for DEFAULT_URL. +DEFAULT_HOST_NAME = None +DEFAULT_URL = None + +HOME_PAGE = 'index.html' +MAILMAN_SITE_LIST = 'mailman' + +# Normally when a site administrator authenticates to a web page with the site +# password, they get a cookie which authorizes them as the list admin. It +# makes me nervous to hand out site auth cookies because if this cookie is +# cracked or intercepted, the intruder will have access to every list on the +# site. OTOH, it's dang handy to not have to re-authenticate to every list on +# the site. Set this value to Yes to allow site admin cookies. +ALLOW_SITE_ADMIN_COOKIES = No + +# Command that is used to convert text/html parts into plain text. This +# should output results to standard output. %(filename)s will contain the +# name of the temporary file that the program should operate on. +HTML_TO_PLAIN_TEXT_COMMAND = '/usr/bin/lynx -dump %(filename)s' + + + +##### +# Virtual domains +##### + +# Set up your virtual host mappings here. This is primarily used for the +# thru-the-web list creation, so its effects are currently fairly limited. +# Use add_virtualhost() call to add new mappings. The keys are strings as +# determined by Utils.get_domain(), the values are as appropriate for +# DEFAULT_HOST_NAME. +VIRTUAL_HOSTS = {} + +# When set to Yes, the listinfo and admin overviews of lists on the machine +# will be confined to only those lists whose web_page_url configuration option +# host is included within the URL by which the page is visited - only those +# "on the virtual host". When set to No, all advertised (i.e. public) lists +# are included in the overview. +VIRTUAL_HOST_OVERVIEW = On + + +# Helper function; use this in your mm_cfg.py files. If optional emailhost is +# omitted it defaults to urlhost with the first name stripped off, e.g. +# +# add_virtualhost('www.dom.ain') +# VIRTUAL_HOST['www.dom.ain'] +# ==> 'dom.ain' +# +def add_virtualhost(urlhost, emailhost=None): + DOT = '.' + if emailhost is None: + emailhost = DOT.join(urlhost.split(DOT)[1:]) + VIRTUAL_HOSTS[urlhost.lower()] = emailhost.lower() + +# And set the default +add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST) + +# Note that you will want to run bin/fix_url.py to change the domain of an +# existing list. bin/fix_url.py must be run within the bin/withlist script, +# like so: bin/withlist -l -r bin/fix_url.py + + + +##### +# Spam avoidance defaults +##### + +# This variable contains a list of 2-tuple of the format (header, regex) which +# the Mailman/Handlers/SpamDetect.py module uses to match against the current +# message. If the regex matches the given header in the current message, then +# it is flagged as spam. header is case-insensitive and should not include +# the trailing colon. regex is always matched with re.IGNORECASE. +# +# Note that the more searching done, the slower the whole process gets. Spam +# detection is run against all messages coming to either the list, or the +# -owners address, unless the message is explicitly approved. +KNOWN_SPAMMERS = [] + + + +##### +# Web UI defaults +##### + +# Almost all the colors used in Mailman's web interface are parameterized via +# the following variables. This lets you easily change the color schemes for +# your preferences without having to do major surgery on the source code. Note +# that in general, the template colors are not included here since it is easy +# enough to override the default template colors via site-wide, vdomain-wide, +# or list-wide specializations. +# +# This section is been moved to CSS (Common Style Sheets). +# - /error/includes/common.css +# - /error/includes/mailman.css +# +# The HTML output layout is been modified too in order to fit the W3C's XHTML +# 1.0 standard. You may use the following variables but they may be deprecated +# in the near feautre. +# +# For more information about the standards used, see: +# - http://www.w3.org/TR/xhtml1 +# - http://www.w3.org/TR/CSS21 + +WEB_BG_COLOR = 'white' # Page background +WEB_HEADER_COLOR = '#99ccff' # Major section headers +WEB_SUBHEADER_COLOR = '#fff0d0' # Minor section headers +WEB_ADMINITEM_COLOR = '#dddddd' # Option field background +WEB_ADMINPW_COLOR = '#99cccc' # Password box color +WEB_ERROR_COLOR = 'red' # Error message foreground +WEB_LINK_COLOR = '' # If true, forces LINK= +WEB_ALINK_COLOR = '' # If true, forces ALINK= +WEB_VLINK_COLOR = '' # If true, forces VLINK= +WEB_HIGHLIGHT_COLOR = '#dddddd' # If true, alternating rows + # in listinfo & admin display + + +##### +# Archive defaults +##### + +# The url template for the public archives. This will be used in several +# places, including the List-Archive: header, links to the archive on the +# list's listinfo page, and on the list's admin page. +# +# This should be a string with "%(listname)s" somewhere in it. Mailman will +# interpolate the name of the list into this. You can also include a +# "%(hostname)s" in the string, into which Mailman will interpolate +# the host name (usually DEFAULT_URL_HOST). +PUBLIC_ARCHIVE_URL = 'http://%(hostname)s/pipermail/%(listname)s' + +# Are archives on or off by default? +DEFAULT_ARCHIVE = On + +# Are archives public or private by default? +# 0=public, 1=private +DEFAULT_ARCHIVE_PRIVATE = 0 + +# ARCHIVE_TO_MBOX +#-1 - do not do any archiving +# 0 - do not archive to mbox, use builtin mailman html archiving only +# 1 - archive to mbox to use an external archiving mechanism only +# 2 - archive to both mbox and builtin mailman html archiving - +# use this to make both external archiving mechanism work and +# mailman's builtin html archiving. the flat mail file can be +# useful for searching, external archivers, etc. +ARCHIVE_TO_MBOX = 2 + +# 0 - yearly +# 1 - monthly +# 2 - quarterly +# 3 - weekly +# 4 - daily +DEFAULT_ARCHIVE_VOLUME_FREQUENCY = 1 +DEFAULT_DIGEST_VOLUME_FREQUENCY = 1 + +# These variables control the use of an external archiver. Normally if +# archiving is turned on (see ARCHIVE_TO_MBOX above and the list's archive* +# attributes) the internal Pipermail archiver is used. This is the default if +# both of these variables are set to No. When either is set, the value should +# be a shell command string which will get passed to os.popen(). This string +# can contain the following substitution strings: +# +# %(listname)s -- gets the internal name of the list +# %(hostname)s -- gets the email hostname for the list +# +# being archived will be substituted for this. Please note that os.popen() is +# used. +# +# Note that if you set one of these variables, you should set both of them +# (they can be the same string). This will mean your external archiver will +# be used regardless of whether public or private archives are selected. +PUBLIC_EXTERNAL_ARCHIVER = No +PRIVATE_EXTERNAL_ARCHIVER = No + +# A filter module that converts from multipart messages to "flat" messages +# (i.e. containing a single payload). This is required for Pipermail, and you +# may want to set it to 0 for external archivers. You can also replace it +# with your own module as long as it contains a process() function that takes +# a MailList object and a Message object. It should raise +# Errors.DiscardMessage if it wants to throw the message away. Otherwise it +# should modify the Message object as necessary. +ARCHIVE_SCRUBBER = 'Mailman.Handlers.Scrubber' + +# Control parameter whether Mailman.Handlers.Scrubber should use message +# attachment's filename as is indicated by the filename parameter or use +# 'attachement-xxx' instead. The default is set True because the applications +# on PC and Mac begin to use longer non-ascii filenames. Historically, it +# was set False in 2.1.6 for backward compatiblity but it was reset to True +# for safer operation in mailman-2.1.7. +SCRUBBER_DONT_USE_ATTACHMENT_FILENAME = True + +# Use of attachment filename extension per se is may be dangerous because +# virus fakes it. You can set this True if you filter the attachment by +# filename extension +SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION = False + +# This variable defines what happens to text/html subparts. They can be +# stripped completely, escaped, or filtered through an external program. The +# legal values are: +# 0 - Strip out text/html parts completely, leaving a notice of the removal in +# the message. If the outer part is text/html, the entire message is +# discarded. +# 1 - Remove any embedded text/html parts, leaving them as HTML-escaped +# attachments which can be separately viewed. Outer text/html parts are +# simply HTML-escaped. +# 2 - Leave it inline, but HTML-escape it +# 3 - Remove text/html as attachments but don't HTML-escape them. Note: this +# is very dangerous because it essentially means anybody can send an HTML +# email to your site containing evil JavaScript or web bugs, or other +# nasty things, and folks viewing your archives will be susceptible. You +# should only consider this option if you do heavy moderation of your list +# postings. +# +# Note: given the current archiving code, it is not possible to leave +# text/html parts inline and un-escaped. I wouldn't think it'd be a good idea +# to do anyway. +# +# The value can also be a string, in which case it is the name of a command to +# filter the HTML page through. The resulting output is left in an attachment +# or as the entirety of the message when the outer part is text/html. The +# format of the string must include a "%(filename)s" which will contain the +# name of the temporary file that the program should operate on. It should +# write the processed message to stdout. Set this to +# HTML_TO_PLAIN_TEXT_COMMAND to specify an HTML to plain text conversion +# program. +ARCHIVE_HTML_SANITIZER = 1 + +# Set this to Yes to enable gzipping of the downloadable archive .txt file. +# Note that this is /extremely/ inefficient, so an alternative is to just +# collect the messages in the associated .txt file and run a cron job every +# night to generate the txt.gz file. See cron/nightly_gzip for details. +GZIP_ARCHIVE_TXT_FILES = No + +# This sets the default `clobber date' policy for the archiver. When a +# message is to be archived either by Pipermail or an external archiver, +# Mailman can modify the Date: header to be the date the message was received +# instead of the Date: in the original message. This is useful if you +# typically receive messages with outrageous dates. Set this to 0 to retain +# the date of the original message, or to 1 to always clobber the date. Set +# it to 2 to perform `smart overrides' on the date; when the date is outside +# ARCHIVER_ALLOWABLE_SANE_DATE_SKEW (either too early or too late), then the +# received date is substituted instead. +ARCHIVER_CLOBBER_DATE_POLICY = 2 +ARCHIVER_ALLOWABLE_SANE_DATE_SKEW = days(15) + +# Pipermail archives contain the raw email addresses of the posting authors. +# Some view this as a goldmine for spam harvesters. Set this to Yes to +# moderately obscure email addresses, but note that this breaks mailto: URLs +# in the archives too. +ARCHIVER_OBSCURES_EMAILADDRS = Yes + +# Pipermail assumes that messages bodies contain US-ASCII text. +# Change this option to define a different character set to be used as +# the default character set for the archive. The term "character set" +# is used in MIME to refer to a method of converting a sequence of +# octets into a sequence of characters. If you change the default +# charset, you might need to add it to VERBATIM_ENCODING below. +DEFAULT_CHARSET = None + +# Most character set encodings require special HTML entity characters to be +# quoted, otherwise they won't look right in the Pipermail archives. However +# some character sets must not quote these characters so that they can be +# rendered properly in the browsers. The primary issue is multi-byte +# encodings where the octet 0x26 does not always represent the & character. +# This variable contains a list of such characters sets which are not +# HTML-quoted in the archives. +VERBATIM_ENCODING = ['iso-2022-jp'] + +# When the archive is public, should Mailman also make the raw Unix mbox file +# publically available? +PUBLIC_MBOX = No + + + +##### +# Delivery defaults +##### + +# Final delivery module for outgoing mail. This handler is used for message +# delivery to the list via the smtpd, and to an individual user. This value +# must be a string naming a module in the Mailman.Handlers package. +# +# WARNING: Sendmail has security holes and should be avoided. In fact, you +# must read the Mailman/Handlers/Sendmail.py file before it will work for +# you. +# +#DELIVERY_MODULE = 'Sendmail' +DELIVERY_MODULE = 'SMTPDirect' + +# MTA should name a module in Mailman/MTA which provides the MTA specific +# functionality for creating and removing lists. Some MTAs like Exim can be +# configured to automatically recognize new lists, in which case the MTA +# variable should be set to None. Use 'Manual' to print new aliases to +# standard out (or send an email to the site list owner) for manual twiddling +# of an /etc/aliases style file. Use 'Postfix' if you are using the Postfix +# MTA -- but then also see POSTFIX_STYLE_VIRTUAL_DOMAINS. +MTA = 'Manual' + +# If you set MTA='Postfix', then you also want to set the following variable, +# depending on whether you're using virtual domains in Postfix, and which +# style of virtual domain you're using. Set this flag to false if you're not +# using virtual domains in Postfix, or if you're using Sendmail-style virtual +# domains (where all addresses are visible in all domains). If you're using +# Postfix-style virtual domains, where aliases should only show up in the +# virtual domain, set this variable to the list of host_name values to write +# separate virtual entries for. I.e. if you run dom1.ain, dom2.ain, and +# dom3.ain, but only dom2 and dom3 are virtual, set this variable to the list +# ['dom2.ain', 'dom3.ain']. Matches are done against the host_name attribute +# of the mailing lists. See README.POSTFIX for details. +POSTFIX_STYLE_VIRTUAL_DOMAINS = [] + +# These variables describe the program to use for regenerating the aliases.db +# and virtual-mailman.db files, respectively, from the associated plain text +# files. The file being updated will be appended to this string (with a +# separating space), so it must be appropriate for os.system(). +POSTFIX_ALIAS_CMD = '/usr/sbin/postalias' +POSTFIX_MAP_CMD = '/usr/sbin/postmap' + +# Ceiling on the number of recipients that can be specified in a single SMTP +# transaction. Set to 0 to submit the entire recipient list in one +# transaction. Only used with the SMTPDirect DELIVERY_MODULE. +SMTP_MAX_RCPTS = 500 + +# Ceiling on the number of SMTP sessions to perform on a single socket +# connection. Some MTAs have limits. Set this to 0 to do as many as we like +# (i.e. your MTA has no limits). Set this to some number great than 0 and +# Mailman will close the SMTP connection and re-open it after this number of +# consecutive sessions. +SMTP_MAX_SESSIONS_PER_CONNECTION = 0 + +# Maximum number of simultaneous subthreads that will be used for SMTP +# delivery. After the recipients list is chunked according to SMTP_MAX_RCPTS, +# each chunk is handed off to the smptd by a separate such thread. If your +# Python interpreter was not built for threads, this feature is disabled. You +# can explicitly disable it in all cases by setting MAX_DELIVERY_THREADS to +# 0. This feature is only supported with the SMTPDirect DELIVERY_MODULE. +# +# NOTE: This is an experimental feature and limited testing shows that it may +# in fact degrade performance, possibly due to Python's global interpreter +# lock. Use with caution. +MAX_DELIVERY_THREADS = 0 + +# SMTP host and port, when DELIVERY_MODULE is 'SMTPDirect'. Make sure the +# host exists and is resolvable (i.e., if it's the default of "localhost" be +# sure there's a localhost entry in your /etc/hosts file!) +SMTPHOST = 'localhost' +SMTPPORT = 0 # default from smtplib + +# Command for direct command pipe delivery to sendmail compatible program, +# when DELIVERY_MODULE is 'Sendmail'. +SENDMAIL_CMD = '/usr/lib/sendmail' + +# Set these variables if you need to authenticate to your NNTP server for +# Usenet posting or reading. If no authentication is necessary, specify None +# for both variables. +NNTP_USERNAME = None +NNTP_PASSWORD = None + +# Set this if you have an NNTP server you prefer gatewayed lists to use. +DEFAULT_NNTP_HOST = '' + +# These variables controls how headers must be cleansed in order to be +# accepted by your NNTP server. Some servers like INN reject messages +# containing prohibited headers, or duplicate headers. The NNTP server may +# reject the message for other reasons, but there's little that can be +# programmatically done about that. See Mailman/Queue/NewsRunner.py +# +# First, these headers (case ignored) are removed from the original message. +NNTP_REMOVE_HEADERS = ['nntp-posting-host', 'nntp-posting-date', 'x-trace', + 'x-complaints-to', 'xref', 'date-received', 'posted', + 'posting-version', 'relay-version', 'received'] + +# Next, these headers are left alone, unless there are duplicates in the +# original message. Any second and subsequent headers are rewritten to the +# second named header (case preserved). +NNTP_REWRITE_DUPLICATE_HEADERS = [ + ('to', 'X-Original-To'), + ('cc', 'X-Original-Cc'), + ('content-transfer-encoding', 'X-Original-Content-Transfer-Encoding'), + ('mime-version', 'X-MIME-Version'), + ] + +# All `normal' messages which are delivered to the entire list membership go +# through this pipeline of handler modules. Lists themselves can override the +# global pipeline by defining a `pipeline' attribute. +GLOBAL_PIPELINE = [ + # These are the modules that do tasks common to all delivery paths. + 'SpamDetect', + 'Approve', + 'Replybot', + 'Moderate', + 'Hold', + 'MimeDel', + 'Scrubber', + 'Emergency', + 'Tagger', + 'CalcRecips', + 'AvoidDuplicates', + 'Cleanse', + 'CleanseDKIM', + 'CookHeaders', + # And now we send the message to the digest mbox file, and to the arch and + # news queues. Runners will provide further processing of the message, + # specific to those delivery paths. + 'ToDigest', + 'ToArchive', + 'ToUsenet', + # Now we'll do a few extra things specific to the member delivery + # (outgoing) path, finally leaving the message in the outgoing queue. + 'AfterDelivery', + 'Acknowledge', + 'ToOutgoing', + ] + +# This is the pipeline which messages sent to the -owner address go through +OWNER_PIPELINE = [ + 'SpamDetect', + 'Replybot', + 'CleanseDKIM', + 'OwnerRecips', + 'ToOutgoing', + ] + + +# This defines syslog() format strings for the SMTPDirect delivery module (see +# DELIVERY_MODULE above). Valid %()s string substitutions include: +# +# time -- the time in float seconds that it took to complete the smtp +# hand-off of the message from Mailman to your smtpd. +# +# size -- the size of the entire message, in bytes +# +# #recips -- the number of actual recipients for this message. +# +# #refused -- the number of smtp refused recipients (use this only in +# SMTP_LOG_REFUSED). +# +# listname -- the `internal' name of the mailing list for this posting +# +# msg_

      -- the value of the delivered message's given header. If +# the message had no such header, then "n/a" will be used. Note though +# that if the message had multiple such headers, then it is undefined +# which will be used. +# +# allmsg_
      - Same as msg_
      above, but if there are multiple +# such headers in the message, they will all be printed, separated by +# comma-space. +# +# sender -- the "sender" of the messages, which will be the From: or +# envelope-sender as determeined by the USE_ENVELOPE_SENDER variable +# below. +# +# The format of the entries is a 2-tuple with the first element naming the +# file in logs/ to print the message to, and the second being a format string +# appropriate for Python's %-style string interpolation. The file name is +# arbitrary; qfiles/ will be created automatically if it does not +# exist. + +# The format of the message printed for every delivered message, regardless of +# whether the delivery was successful or not. Set to None to disable the +# printing of this log message. +SMTP_LOG_EVERY_MESSAGE = ( + 'smtp', + '%(msg_message-id)s smtp to %(listname)s for %(#recips)d recips, completed in %(time).3f seconds') + +# This will only be printed if there were no immediate smtp failures. +# Mutually exclusive with SMTP_LOG_REFUSED. +SMTP_LOG_SUCCESS = ( + 'post', + 'post to %(listname)s from %(sender)s, size=%(size)d, message-id=%(msg_message-id)s, success') + +# This will only be printed if there were any addresses which encountered an +# immediate smtp failure. Mutually exclusive with SMTP_LOG_SUCCESS. +SMTP_LOG_REFUSED = ( + 'post', + 'post to %(listname)s from %(sender)s, size=%(size)d, message-id=%(msg_message-id)s, %(#refused)d failures') + +# This will be logged for each specific recipient failure. Additional %()s +# keys are: +# +# recipient -- the failing recipient address +# failcode -- the smtp failure code +# failmsg -- the actual smtp message, if available +SMTP_LOG_EACH_FAILURE = ( + 'smtp-failure', + 'delivery to %(recipient)s failed with code %(failcode)d: %(failmsg)s') + +# These variables control the format and frequency of VERP-like delivery for +# better bounce detection. VERP is Variable Envelope Return Path, defined +# here: +# +# http://cr.yp.to/proto/verp.txt +# +# This involves encoding the address of the recipient as we (Mailman) know it +# into the envelope sender address (i.e. the SMTP `MAIL FROM:' address). +# Thus, no matter what kind of forwarding the recipient has in place, should +# it eventually bounce, we will receive an unambiguous notice of the bouncing +# address. +# +# However, we're technically only "VERP-like" because we're doing the envelope +# sender encoding in Mailman, not in the MTA. We do require cooperation from +# the MTA, so you must be sure your MTA can be configured for extended address +# semantics. +# +# The first variable describes how to encode VERP envelopes. It must contain +# these three string interpolations: +# +# %(bounces)s -- the list-bounces mailbox will be set here +# %(mailbox)s -- the recipient's mailbox will be set here +# %(host)s -- the recipient's host name will be set here +# +# This example uses the default below. +# +# FQDN list address is: mylist@dom.ain +# Recipient is: aperson@a.nother.dom +# +# The envelope sender will be mylist-bounces+aperson=a.nother.dom@dom.ain +# +# Note that your MTA /must/ be configured to deliver such an addressed message +# to mylist-bounces! +VERP_FORMAT = '%(bounces)s+%(mailbox)s=%(host)s' + +# The second describes a regular expression to unambiguously decode such an +# address, which will be placed in the To: header of the bounce message by the +# bouncing MTA. Getting this right is critical -- and tricky. Learn your +# Python regular expressions. It must define exactly three named groups, +# bounces, mailbox and host, with the same definition as above. It will be +# compiled case-insensitively. +VERP_REGEXP = r'^(?P[^+]+?)\+(?P[^=]+)=(?P[^@]+)@.*$' + +# VERP format and regexp for probe messages +VERP_PROBE_FORMAT = '%(bounces)s+%(token)s' +VERP_PROBE_REGEXP = r'^(?P[^+]+?)\+(?P[^@]+)@.*$' +# Set this Yes to activate VERP probe for disabling by bounce +VERP_PROBES = No + +# A perfect opportunity for doing VERP is the password reminders, which are +# already addressed individually to each recipient. Set this to Yes to enable +# VERPs on all password reminders. +VERP_PASSWORD_REMINDERS = No + +# Another good opportunity is when regular delivery is personalized. Here +# again, we're already incurring the performance hit for addressing each +# individual recipient. Set this to Yes to enable VERPs on all personalized +# regular deliveries (personalized digests aren't supported yet). +VERP_PERSONALIZED_DELIVERIES = No + +# And finally, we can VERP normal, non-personalized deliveries. However, +# because it can be a significant performance hit, we allow you to decide how +# often to VERP regular deliveries. This is the interval, in number of +# messages, to do a VERP recipient address. The same variable controls both +# regular and digest deliveries. Set to 0 to disable occasional VERPs, set to +# 1 to VERP every delivery, or to some number > 1 for only occasional VERPs. +VERP_DELIVERY_INTERVAL = 0 + +# For nicer confirmation emails, use a VERP-like format which encodes the +# confirmation cookie in the reply address. This lets us put a more user +# friendly Subject: on the message, but requires cooperation from the MTA. +# Format is like VERP_FORMAT above, but with the following substitutions: +# +# %(addr)s -- the list-confirm mailbox will be set here +# %(cookie)s -- the confirmation cookie will be set here +VERP_CONFIRM_FORMAT = '%(addr)s+%(cookie)s' + +# This is analogous to VERP_REGEXP, but for splitting apart the +# VERP_CONFIRM_FORMAT. MUAs have been observed that mung +# From: local_part@host +# into +# To: "local_part" +# when replying, so we skip everything up to '<' if any. +VERP_CONFIRM_REGEXP = r'^(.*<)?(?P[^+]+?)\+(?P[^@]+)@.*$' + +# Set this to Yes to enable VERP-like (more user friendly) confirmations +VERP_CONFIRMATIONS = No + +# This is the maximum number of automatic responses sent to an address because +# of -request messages or posting hold messages. This limit prevents response +# loops between Mailman and misconfigured remote email robots. Mailman +# already inhibits automatic replies to any message labeled with a header +# "Precendence: bulk|list|junk". This is a fallback safety valve so it should +# be set fairly high. Set to 0 for no limit (probably useful only for +# debugging). +MAX_AUTORESPONSES_PER_DAY = 10 + + + +##### +# Qrunner defaults +##### + +# Which queues should the qrunner master watchdog spawn? This is a list of +# 2-tuples containing the name of the qrunner class (which must live in a +# module of the same name within the Mailman.Queue package), and the number of +# parallel processes to fork for each qrunner. If more than one process is +# used, each will take an equal subdivision of the hash space. + +# BAW: Eventually we may support weighted hash spaces. +# BAW: Although not enforced, the # of slices must be a power of 2 + +QRUNNERS = [ + ('ArchRunner', 1), # messages for the archiver + ('BounceRunner', 1), # for processing the qfile/bounces directory + ('CommandRunner', 1), # commands and bounces from the outside world + ('IncomingRunner', 1), # posts from the outside world + ('NewsRunner', 1), # outgoing messages to the nntpd + ('OutgoingRunner', 1), # outgoing messages to the smtpd + ('VirginRunner', 1), # internally crafted (virgin birth) messages + ('RetryRunner', 1), # retry temporarily failed deliveries + ] + +# Set this to Yes to use the `Maildir' delivery option. If you change this +# you will need to re-run bin/genaliases for MTAs that don't use list +# auto-detection. +# +# WARNING: If you want to use Maildir delivery, you /must/ start Mailman's +# qrunner as root, or you will get permission problems. +# +# NOTE: Maildir delivery is experimental for Mailman 2.1. +USE_MAILDIR = No +# NOTE: If you set USE_MAILDIR = Yes, add the following line to your mm_cfg.py +# file (uncommented of course!) +# QRUNNERS.append(('MaildirRunner', 1)) + +# After processing every file in the qrunner's slice, how long should the +# runner sleep for before checking the queue directory again for new files? +# This can be a fraction of a second, or zero to check immediately +# (essentially busy-loop as fast as possible). +QRUNNER_SLEEP_TIME = seconds(1) + +# When a message that is unparsable (by the email package) is received, what +# should we do with it? The most common cause of unparsable messages is +# broken MIME encapsulation, and the most common cause of that is viruses like +# Nimda. Set this variable to No to discard such messages, or to Yes to store +# them in qfiles/bad subdirectory. +QRUNNER_SAVE_BAD_MESSAGES = Yes + +# This flag causes Mailman to fsync() its data files after writing and +# flushing its contents. While this ensures the data is written to disk, +# avoiding data loss, it may be a performance killer. Note that this flag +# affects both message pickles and MailList config.pck files. +SYNC_AFTER_WRITE = No + + + +##### +# General defaults +##### + +# The default language for this server. Whenever we can't figure out the list +# context or user context, we'll fall back to using this language. See +# LC_DESCRIPTIONS below for legal values. +DEFAULT_SERVER_LANGUAGE = 'en' + +# When allowing only members to post to a mailing list, how is the sender of +# the message determined? If this variable is set to Yes, then first the +# message's envelope sender is used, with a fallback to the sender if there is +# no envelope sender. Set this variable to No to always use the sender. +# +# The envelope sender is set by the SMTP delivery and is thus less easily +# spoofed than the sender, which is typically just taken from the From: header +# and thus easily spoofed by the end-user. However, sometimes the envelope +# sender isn't set correctly and this will manifest itself by postings being +# held for approval even if they appear to come from a list member. If you +# are having this problem, set this variable to No, but understand that some +# spoofed messages may get through. +USE_ENVELOPE_SENDER = No + +# Membership tests for posting purposes are usually performed by looking at a +# set of headers, passing the test if any of their values match a member of +# the list. Headers are checked in the order given in this variable. The +# value None means use the From_ (envelope sender) header. Field names are +# case insensitive. +SENDER_HEADERS = ('from', None, 'reply-to', 'sender') + +# How many members to display at a time on the admin cgi to unsubscribe them +# or change their options? +DEFAULT_ADMIN_MEMBER_CHUNKSIZE = 30 + +# how many bytes of a held message post should be displayed in the admindb web +# page? Use a negative number to indicate the entire message, regardless of +# size (though this will slow down rendering those pages). +ADMINDB_PAGE_TEXT_LIMIT = 4096 + +# Set this variable to Yes to allow list owners to delete their own mailing +# lists. You may not want to give them this power, in which case, setting +# this variable to No instead requires list removal to be done by the site +# administrator, via the command line script bin/rmlist. +OWNERS_CAN_DELETE_THEIR_OWN_LISTS = No + +# Set this variable to Yes to allow list owners to set the "personalized" +# flags on their mailing lists. Turning these on tells Mailman to send +# separate email messages to each user instead of batching them together for +# delivery to the MTA. This gives each member a more personalized message, +# but can have a heavy impact on the performance of your system. +OWNERS_CAN_ENABLE_PERSONALIZATION = No + +# Should held messages be saved on disk as Python pickles or as plain text? +# The former is more efficient since we don't need to go through the +# parse/generate roundtrip each time, but the latter might be preferred if you +# want to edit the held message on disk. +HOLD_MESSAGES_AS_PICKLES = Yes + +# This variable controls the order in which list-specific category options are +# presented in the admin cgi page. +ADMIN_CATEGORIES = [ + # First column + 'general', 'passwords', 'language', 'members', 'nondigest', 'digest', + # Second column + 'privacy', 'bounce', 'archive', 'gateway', 'autoreply', + 'contentfilter', 'topics', + ] + +# See "Bitfield for user options" below; make this a sum of those options, to +# make all new members of lists start with those options flagged. We assume +# by default that people don't want to receive two copies of posts. Note +# however that the member moderation flag's initial value is controlled by the +# list's config variable default_member_moderation. +DEFAULT_NEW_MEMBER_OPTIONS = 256 + +# Specify the type of passwords to use, when Mailman generates the passwords +# itself, as would be the case for membership requests where the user did not +# fill in a password, or during list creation, when auto-generation of admin +# passwords was selected. +# +# Set this value to Yes for classic Mailman user-friendly(er) passwords. +# These generate semi-pronounceable passwords which are easier to remember. +# Set this value to No to use more cryptographically secure, but harder to +# remember, passwords -- if your operating system and Python version support +# the necessary feature (specifically that /dev/urandom be available). +USER_FRIENDLY_PASSWORDS = Yes +# This value specifies the default lengths of member and list admin passwords +MEMBER_PASSWORD_LENGTH = 10 +ADMIN_PASSWORD_LENGTH = 10 + + + +##### +# List defaults. NOTE: Changing these values does NOT change the +# configuration of an existing list. It only defines the default for new +# lists you subsequently create. +##### + +# Should a list, by default be advertised? What is the default maximum number +# of explicit recipients allowed? What is the default maximum message size +# allowed? +DEFAULT_LIST_ADVERTISED = Yes +DEFAULT_MAX_NUM_RECIPIENTS = 10 +DEFAULT_MAX_MESSAGE_SIZE = 40 # KB + +# These format strings will be expanded w.r.t. the dictionary for the +# mailing list instance. +DEFAULT_SUBJECT_PREFIX = "[%(real_name)s] " +# DEFAULT_SUBJECT_PREFIX = "[%(real_name)s %%d]" # for numbering +DEFAULT_MSG_HEADER = "" +DEFAULT_MSG_FOOTER = """_______________________________________________ +%(real_name)s mailing list +%(real_name)s@%(host_name)s +%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s +""" + +# Where to put subject prefix for 'Re:' messages: +# +# old style: Re: [prefix] test +# new style: [prefix 123] Re: test ... (number is optional) +# +# Old style is default for backward compatibility. New style is forced if a +# list owner set %d (numbering) in prefix. If the site owner had applied new +# style patch (from SF patch area) before, he/she may want to set this No in +# mm_cfg.py. +OLD_STYLE_PREFIXING = Yes + +# Scrub regular delivery +DEFAULT_SCRUB_NONDIGEST = False + +# Mail command processor will ignore mail command lines after designated max. +DEFAULT_MAIL_COMMANDS_MAX_LINES = 25 + +# Is the list owner notified of admin requests immediately by mail, as well as +# by daily pending-request reminder? +DEFAULT_ADMIN_IMMED_NOTIFY = Yes + +# Is the list owner notified of subscribes/unsubscribes? +DEFAULT_ADMIN_NOTIFY_MCHANGES = No + +# Discard held messages after this days +DEFAULT_MAX_DAYS_TO_HOLD = 0 + +# Should list members, by default, have their posts be moderated? +DEFAULT_DEFAULT_MEMBER_MODERATION = No + +# Should non-member posts which are auto-discarded also be forwarded to the +# moderators? +DEFAULT_FORWARD_AUTO_DISCARDS = Yes + +# What shold happen to non-member posts which are do not match explicit +# non-member actions? +# 0 = Accept +# 1 = Hold +# 2 = Reject +# 3 = Discard +DEFAULT_GENERIC_NONMEMBER_ACTION = 1 + +# Bounce if 'To:', 'Cc:', or 'Resent-To:' fields don't explicitly name list? +# This is an anti-spam measure +DEFAULT_REQUIRE_EXPLICIT_DESTINATION = Yes + +# Alternate names acceptable as explicit destinations for this list. +DEFAULT_ACCEPTABLE_ALIASES =""" +""" +# For mailing lists that have only other mailing lists for members: +DEFAULT_UMBRELLA_LIST = No + +# For umbrella lists, the suffix for the account part of address for +# administrative notices (subscription confirmations, password reminders): +DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX = "-owner" + +# This variable controls whether monthly password reminders are sent. +DEFAULT_SEND_REMINDERS = Yes + +# Send welcome messages to new users? +DEFAULT_SEND_WELCOME_MSG = Yes + +# Send goodbye messages to unsubscribed members? +DEFAULT_SEND_GOODBYE_MSG = Yes + +# Wipe sender information, and make it look like the list-admin +# address sends all messages +DEFAULT_ANONYMOUS_LIST = No + +# {header-name: regexp} spam filtering - we include some for example sake. +DEFAULT_BOUNCE_MATCHING_HEADERS = """ +# Lines that *start* with a '#' are comments. +to: friend@public.com +message-id: relay.comanche.denmark.eu +from: list@listme.com +from: .*@uplinkpro.com +""" + +# Mailman can be configured to "munge" Reply-To: headers for any passing +# messages. One the one hand, there are a lot of good reasons not to munge +# Reply-To: but on the other, people really seem to want this feature. See +# the help for reply_goes_to_list in the web UI for links discussing the +# issue. +# 0 - Reply-To: not munged +# 1 - Reply-To: set back to the list +# 2 - Reply-To: set to an explicit value (reply_to_address) +DEFAULT_REPLY_GOES_TO_LIST = 0 + +# Mailman can be configured to strip any existing Reply-To: header, or simply +# extend any existing Reply-To: with one based on the above setting. +DEFAULT_FIRST_STRIP_REPLY_TO = No + +# SUBSCRIBE POLICY +# 0 - open list (only when ALLOW_OPEN_SUBSCRIBE is set to 1) ** +# 1 - confirmation required for subscribes +# 2 - admin approval required for subscribes +# 3 - both confirmation and admin approval required +# +# ** please do not choose option 0 if you are not allowing open +# subscribes (next variable) +DEFAULT_SUBSCRIBE_POLICY = 1 + +# Does this site allow completely unchecked subscriptions? +ALLOW_OPEN_SUBSCRIBE = No + +# The default policy for unsubscriptions. 0 (unmoderated unsubscribes) is +# highly recommended! +# 0 - unmoderated unsubscribes +# 1 - unsubscribes require approval +DEFAULT_UNSUBSCRIBE_POLICY = 0 + +# Private_roster == 0: anyone can see, 1: members only, 2: admin only. +DEFAULT_PRIVATE_ROSTER = 1 + +# When exposing members, make them unrecognizable as email addrs, so +# web-spiders can't pick up addrs for spam purposes. +DEFAULT_OBSCURE_ADDRESSES = Yes + +# RFC 2369 defines List-* headers which are added to every message sent +# through to the mailing list membership. These are a very useful aid to end +# users and should always be added. However, not all MUAs are compliant and +# if a list's membership has many such users, they may clamor for these +# headers to be suppressed. By setting this variable to Yes, list owners will +# be given the option to suppress these headers. By setting it to No, list +# owners will not be given the option to suppress these headers (although some +# header suppression may still take place, i.e. for announce-only lists, or +# lists with no archives). +ALLOW_RFC2369_OVERRIDES = Yes + +# Defaults for content filtering on mailing lists. DEFAULT_FILTER_CONTENT is +# a flag which if set to true, turns on content filtering. +DEFAULT_FILTER_CONTENT = No + +# DEFAULT_FILTER_MIME_TYPES is a list of MIME types to be removed. This is a +# list of strings of the format "maintype/subtype" or simply "maintype". +# E.g. "text/html" strips all html attachments while "image" strips all image +# types regardless of subtype (jpeg, gif, etc.). +DEFAULT_FILTER_MIME_TYPES = [] + +# DEFAULT_PASS_MIME_TYPES is a list of MIME types to be passed through. +# Format is the same as DEFAULT_FILTER_MIME_TYPES +DEFAULT_PASS_MIME_TYPES = ['multipart/mixed', + 'multipart/alternative', + 'text/plain'] + +# DEFAULT_FILTER_FILENAME_EXTENSIONS is a list of filename extensions to be +# removed. It is useful because many viruses fake their content-type as +# harmless ones while keep their extension as executable and expect to be +# executed when victims 'open' them. +DEFAULT_FILTER_FILENAME_EXTENSIONS = [ + 'exe', 'bat', 'cmd', 'com', 'pif', 'scr', 'vbs', 'cpl' + ] + +# DEFAULT_PASS_FILENAME_EXTENSIONS is a list of filename extensions to be +# passed through. Format is the same as DEFAULT_FILTER_FILENAME_EXTENSIONS. +DEFAULT_PASS_FILENAME_EXTENSIONS = [] + +# Replace multipart/alternative with its first alternative. +DEFAULT_COLLAPSE_ALTERNATIVES = Yes + +# Whether text/html should be converted to text/plain after content filtering +# is performed. Conversion is done according to HTML_TO_PLAIN_TEXT_COMMAND +DEFAULT_CONVERT_HTML_TO_PLAINTEXT = Yes + +# Default action to take on filtered messages. +# 0 = Discard, 1 = Reject, 2 = Forward, 3 = Preserve +DEFAULT_FILTER_ACTION = 0 + +# Whether to allow list owners to preserve content filtered messages to a +# special queue on the disk. +OWNERS_CAN_PRESERVE_FILTERED_MESSAGES = Yes + +# Check for administrivia in messages sent to the main list? +DEFAULT_ADMINISTRIVIA = Yes + + + +##### +# Digestification defaults. Same caveat applies here as with list defaults. +##### + +# Will list be available in non-digested form? +DEFAULT_NONDIGESTABLE = Yes + +# Will list be available in digested form? +DEFAULT_DIGESTABLE = Yes +DEFAULT_DIGEST_HEADER = "" +DEFAULT_DIGEST_FOOTER = DEFAULT_MSG_FOOTER + +DEFAULT_DIGEST_IS_DEFAULT = No +DEFAULT_MIME_IS_DEFAULT_DIGEST = No +DEFAULT_DIGEST_SIZE_THRESHHOLD = 30 # KB +DEFAULT_DIGEST_SEND_PERIODIC = Yes + +# Headers which should be kept in both RFC 1153 (plain) and MIME digests. RFC +# 1153 also specifies these headers in this exact order, so order matters. +MIME_DIGEST_KEEP_HEADERS = [ + 'Date', 'From', 'To', 'Cc', 'Subject', 'Message-ID', 'Keywords', + # I believe we should also keep these headers though. + 'In-Reply-To', 'References', 'Content-Type', 'MIME-Version', + 'Content-Transfer-Encoding', 'Precedence', 'Reply-To', + # Mailman 2.0 adds these headers + 'Message', + ] + +PLAIN_DIGEST_KEEP_HEADERS = [ + 'Message', 'Date', 'From', + 'Subject', 'To', 'Cc', + 'Message-ID', 'Keywords', + 'Content-Type', + ] + + + +##### +# Bounce processing defaults. Same caveat applies here as with list defaults. +##### + +# Should we do any bounced mail response at all? +DEFAULT_BOUNCE_PROCESSING = Yes + +# How often should the bounce qrunner process queued detected bounces? +REGISTER_BOUNCES_EVERY = minutes(15) + +# Bounce processing works like this: when a bounce from a member is received, +# we look up the `bounce info' for this member. If there is no bounce info, +# this is the first bounce we've received from this member. In that case, we +# record today's date, and initialize the bounce score (see below for initial +# value). +# +# If there is existing bounce info for this member, we look at the last bounce +# receive date. If this date is farther away from today than the `bounce +# expiration interval', we throw away all the old data and initialize the +# bounce score as if this were the first bounce from the member. +# +# Otherwise, we increment the bounce score. If we can determine whether the +# bounce was soft or hard (i.e. transient or fatal), then we use a score value +# of 0.5 for soft bounces and 1.0 for hard bounces. Note that we only score +# one bounce per day. If the bounce score is then greater than the `bounce +# threshold' we disable the member's address. +# +# After disabling the address, we can send warning messages to the member, +# providing a confirmation cookie/url for them to use to re-enable their +# delivery. After a configurable period of time, we'll delete the address. +# When we delete the address due to bouncing, we'll send one last message to +# the member. + +# Bounce scores greater than this value get disabled. +DEFAULT_BOUNCE_SCORE_THRESHOLD = 5.0 + +# Bounce information older than this interval is considered stale, and is +# discarded. +DEFAULT_BOUNCE_INFO_STALE_AFTER = days(7) + +# The number of notifications to send to the disabled/removed member before we +# remove them from the list. A value of 0 means we remove the address +# immediately (with one last notification). Note that the first one is sent +# upon change of status to disabled. +DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS = 3 + +# The interval of time between disabled warnings. +DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL = days(7) + +# Does the list owner get messages to the -bounces (and -admin) address that +# failed to match by the bounce detector? +DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER = Yes + +# Notifications on bounce actions. The first specifies whether the list owner +# should get a notification when a member is disabled due to bouncing, while +# the second specifies whether the owner should get one when the member is +# removed due to bouncing. +DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE = Yes +DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL = Yes + + + +##### +# General time limits +##### + +# Default length of time a pending request is live before it is evicted from +# the pending database. +PENDING_REQUEST_LIFE = days(3) + +# How long should messages which have delivery failures continue to be +# retried? After this period of time, a message that has failed recipients +# will be dequeued and those recipients will never receive the message. +DELIVERY_RETRY_PERIOD = days(5) + +# How long should we wait before we retry a temporary delivery failure? +DELIVERY_RETRY_WAIT = hours(1) + + + +##### +# Lock management defaults +##### + +# These variables control certain aspects of lock acquisition and retention. +# They should be tuned as appropriate for your environment. All variables are +# specified in units of floating point seconds. YOU MAY NEED TO TUNE THESE +# VARIABLES DEPENDING ON THE SIZE OF YOUR LISTS, THE PERFORMANCE OF YOUR +# HARDWARE, NETWORK AND GENERAL MAIL HANDLING CAPABILITIES, ETC. + +# Set this to On to turn on MailList object lock debugging messages, which +# will be written to logs/locks. If you think you're having lock problems, or +# just want to tune the locks for your system, turn on lock debugging. +LIST_LOCK_DEBUGGING = Off + +# This variable specifies how long the lock will be retained for a specific +# operation on a mailing list. Watch your logs/lock file and if you see a lot +# of lock breakages, you might need to bump this up. However if you set this +# too high, a faulty script (or incorrect use of bin/withlist) can prevent the +# list from being used until the lifetime expires. This is probably one of +# the most crucial tuning variables in the system. +LIST_LOCK_LIFETIME = hours(5) + +# This variable specifies how long an attempt will be made to acquire a list +# lock by the incoming qrunner process. If the lock acquisition times out, +# the message will be re-queued for later delivery. +LIST_LOCK_TIMEOUT = seconds(10) + +# Set this to On to turn on lock debugging messages for the pending requests +# database, which will be written to logs/locks. If you think you're having +# lock problems, or just want to tune the locks for your system, turn on lock +# debugging. +PENDINGDB_LOCK_DEBUGGING = Off + + + +##### +# Nothing below here is user configurable. Most of these values are in this +# file for internal system convenience. Don't change any of them or override +# any of them in your mm_cfg.py file! +##### + +# These directories are used to find various important files in the Mailman +# installation. PREFIX and EXEC_PREFIX are set by configure and should point +# to the installation directory of the Mailman package. +PYTHON = '/usr/bin/python' +PREFIX = '/usr/lib/mailman' +EXEC_PREFIX = '${prefix}' +VAR_PREFIX = '/var/lib/mailman' + +# Work around a bogus autoconf 2.12 bug +if EXEC_PREFIX == '${prefix}': + EXEC_PREFIX = PREFIX + +# CGI extension, change using configure script +CGIEXT = '' + +# Group id that group-owns the Mailman installation +MAILMAN_USER = 'mailman' +MAILMAN_GROUP = 'mailman' + +# Enumeration for Mailman cgi widget types +Toggle = 1 +Radio = 2 +String = 3 +Text = 4 +Email = 5 +EmailList = 6 +Host = 7 +Number = 8 +FileUpload = 9 +Select = 10 +Topics = 11 +Checkbox = 12 +# An "extended email list". Contents must be an email address or a ^-prefixed +# regular expression. Used in the sender moderation text boxes. +EmailListEx = 13 +# Extended spam filter widget +HeaderFilter = 14 + +# Actions +DEFER = 0 +APPROVE = 1 +REJECT = 2 +DISCARD = 3 +SUBSCRIBE = 4 +UNSUBSCRIBE = 5 +ACCEPT = 6 +HOLD = 7 + +# Standard text field width +TEXTFIELDWIDTH = 40 + +# Bitfield for user options. See DEFAULT_NEW_MEMBER_OPTIONS above to set +# defaults for all new lists. +Digests = 0 # handled by other mechanism, doesn't need a flag. +DisableDelivery = 1 # Obsolete; use set/getDeliveryStatus() +DontReceiveOwnPosts = 2 # Non-digesters only +AcknowledgePosts = 4 +DisableMime = 8 # Digesters only +ConcealSubscription = 16 +SuppressPasswordReminder = 32 +ReceiveNonmatchingTopics = 64 +Moderate = 128 +DontReceiveDuplicates = 256 + +# A mapping between short option tags and their flag +OPTINFO = {'hide' : ConcealSubscription, + 'nomail' : DisableDelivery, + 'ack' : AcknowledgePosts, + 'notmetoo': DontReceiveOwnPosts, + 'digest' : 0, + 'plain' : DisableMime, + 'nodupes' : DontReceiveDuplicates + } + +# Authentication contexts. +# +# Mailman defines the following roles: + +# - User, a normal user who has no permissions except to change their personal +# option settings +# - List creator, someone who can create and delete lists, but cannot +# (necessarily) configure the list. +# - List moderator, someone who can tend to pending requests such as +# subscription requests, or held messages +# - List administrator, someone who has total control over a list, can +# configure it, modify user options for members of the list, subscribe and +# unsubscribe members, etc. +# - Site administrator, someone who has total control over the entire site and +# can do any of the tasks mentioned above. This person usually also has +# command line access. + +UnAuthorized = 0 +AuthUser = 1 # Joe Shmoe User +AuthCreator = 2 # List Creator / Destroyer +AuthListAdmin = 3 # List Administrator (total control over list) +AuthListModerator = 4 # List Moderator (can only handle held requests) +AuthSiteAdmin = 5 # Site Administrator (total control over everything) + +# Useful directories +LIST_DATA_DIR = os.path.join(VAR_PREFIX, 'lists') +LOG_DIR = '/var/log/mailman' +LOCK_DIR = '/var/lock/mailman' +CONFIG_DIR = '/etc/mailman' +DATA_DIR = os.path.join(VAR_PREFIX, 'data') +PID_DIR = '/var/run/mailman' +SPAM_DIR = os.path.join(VAR_PREFIX, 'spam') +WRAPPER_DIR = os.path.join(EXEC_PREFIX, 'mail') +BIN_DIR = os.path.join(PREFIX, 'bin') +SCRIPTS_DIR = os.path.join(PREFIX, 'scripts') +TEMPLATE_DIR = os.path.join(PREFIX, 'templates') +MESSAGES_DIR = os.path.join(PREFIX, 'messages') +PUBLIC_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX, 'archives', 'public') +PRIVATE_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX, 'archives', 'private') + +# Directories used by the qrunner subsystem +QUEUE_DIR = '/var/spool/mailman' +INQUEUE_DIR = os.path.join(QUEUE_DIR, 'in') +OUTQUEUE_DIR = os.path.join(QUEUE_DIR, 'out') +CMDQUEUE_DIR = os.path.join(QUEUE_DIR, 'commands') +BOUNCEQUEUE_DIR = os.path.join(QUEUE_DIR, 'bounces') +NEWSQUEUE_DIR = os.path.join(QUEUE_DIR, 'news') +ARCHQUEUE_DIR = os.path.join(QUEUE_DIR, 'archive') +SHUNTQUEUE_DIR = os.path.join(QUEUE_DIR, 'shunt') +VIRGINQUEUE_DIR = os.path.join(QUEUE_DIR, 'virgin') +BADQUEUE_DIR = os.path.join(QUEUE_DIR, 'bad') +RETRYQUEUE_DIR = os.path.join(QUEUE_DIR, 'retry') +MAILDIR_DIR = os.path.join(QUEUE_DIR, 'maildir') + +# Other useful files +PIDFILE = os.path.join(PID_DIR, 'master-qrunner.pid') +SITE_PW_FILE = os.path.join(CONFIG_DIR, 'adm.pw') +LISTCREATOR_PW_FILE = os.path.join(CONFIG_DIR, 'creator.pw') + +# Import a bunch of version numbers +from Version import * + +# Vgg: Language descriptions and charsets dictionary, any new supported +# language must have a corresponding entry here. Key is the name of the +# directories that hold the localized texts. Data are tuples with first +# element being the description, as described in the catalogs, and second +# element is the language charset. I have chosen code from /usr/share/locale +# in my GNU/Linux. :-) +def _(s): + return s + +LC_DESCRIPTIONS = {} + +def add_language(code, description, charset): + LC_DESCRIPTIONS[code] = (description, charset) + +add_language('ar', _('Arabic'), 'utf-8') +add_language('ca', _('Catalan'), 'iso-8859-1') +add_language('cs', _('Czech'), 'iso-8859-2') +add_language('da', _('Danish'), 'iso-8859-1') +add_language('de', _('German'), 'iso-8859-1') +add_language('en', _('English (USA)'), 'us-ascii') +add_language('es', _('Spanish (Spain)'), 'iso-8859-1') +add_language('et', _('Estonian'), 'iso-8859-15') +add_language('eu', _('Euskara'), 'iso-8859-15') # Basque +add_language('fi', _('Finnish'), 'iso-8859-1') +add_language('fr', _('French'), 'iso-8859-1') +add_language('hr', _('Croatian'), 'iso-8859-2') +add_language('hu', _('Hungarian'), 'iso-8859-2') +add_language('ia', _('Interlingua'), 'iso-8859-15') +add_language('it', _('Italian'), 'iso-8859-1') +add_language('ja', _('Japanese'), 'euc-jp') +add_language('ko', _('Korean'), 'euc-kr') +add_language('lt', _('Lithuanian'), 'iso-8859-13') +add_language('nl', _('Dutch'), 'iso-8859-1') +add_language('no', _('Norwegian'), 'iso-8859-1') +add_language('pl', _('Polish'), 'iso-8859-2') +add_language('pt', _('Portuguese'), 'iso-8859-1') +add_language('pt_BR', _('Portuguese (Brazil)'), 'iso-8859-1') +add_language('ro', _('Romanian'), 'iso-8859-2') +add_language('ru', _('Russian'), 'koi8-r') +add_language('sr', _('Serbian'), 'utf-8') +add_language('sl', _('Slovenian'), 'iso-8859-2') +add_language('sv', _('Swedish'), 'iso-8859-1') +add_language('tr', _('Turkish'), 'iso-8859-9') +add_language('uk', _('Ukrainian'), 'utf-8') +add_language('vi', _('Vietnamese'), 'utf-8') +add_language('zh_CN', _('Chinese (China)'), 'utf-8') +add_language('zh_TW', _('Chinese (Taiwan)'), 'utf-8') + +del _ diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Archive.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Archive.py new file mode 100644 index 0000000..a3de3fc --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Archive.py @@ -0,0 +1,44 @@ +# Copyright (C) 2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from Mailman import mm_cfg +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + + + +class Archive(GUIBase): + def GetConfigCategory(self): + return 'archive', _('Archiving') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'archive': + return None + return [ + '

      ' + _("List traffic archival policies.") + '

      ', + + ('archive', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Archive messages?')), + + ('archive_private', mm_cfg.Radio, (_('Public'), _('Private')), 0, + _('Is archive file source for public or private archival?')), + + ('archive_volume_frequency', mm_cfg.Radio, + (_('Yearly'), _('Monthly'), _('Quarterly'), + _('Weekly'), _('Daily')), + 0, + _('How often should a new archive volume be started?')), + ] diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Autoresponse.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Autoresponse.py new file mode 100644 index 0000000..9fb9679 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Autoresponse.py @@ -0,0 +1,95 @@ +# Copyright (C) 2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Administrative GUI for the autoresponder.""" + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + +# These are the allowable string substitution variables +ALLOWEDS = ('listname', 'listurl', 'requestemail', 'adminemail', 'owneremail') + + + +class Autoresponse(GUIBase): + def GetConfigCategory(self): + return 'autoreply', _('Auto-responder') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'autoreply': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + return [ + '

      ' + _('Auto-responder characteristics.') + '

      ' + + '

      ' + _('In the text fields below, string interpolation is performed with the following key/value substitutions:') + + '

      ' + + '''
        +
      • listname - ''' + _('gets the name of the mailing list') + '''
      • +
      • listurl - ''' + _("gets the list's listinfo URL") + '''
      • +
      • requestemail - ''' + _("gets the list's -request address") + '''
      • +
      • owneremail - ''' + _("gets the list's -owner address") + '''
      • +
      + +

      ''' + _('For each text field, you can either enter the text directly into the text box, or you can specify a file on your local system to upload as the text.') + '''

      ''', + + ('autorespond_postings', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('''Should Mailman send an auto-response to mailing list + posters?''')), + + ('autoresponse_postings_text', mm_cfg.FileUpload, + (6, WIDTH), 0, + _('Auto-response text to send to mailing list posters.')), + + ('autorespond_admin', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('''Should Mailman send an auto-response to emails sent to the + -owner address?''')), + + ('autoresponse_admin_text', mm_cfg.FileUpload, + (6, WIDTH), 0, + _('Auto-response text to send to -owner emails.')), + + ('autorespond_requests', mm_cfg.Radio, + (_('No'), _('Yes, w/discard'), _('Yes, w/forward')), 0, + _('''Should Mailman send an auto-response to emails sent to the + -request address? If you choose yes, decide whether you want + Mailman to discard the original email, or forward it on to the + system as a normal mail command.''')), + + ('autoresponse_request_text', mm_cfg.FileUpload, + (6, WIDTH), 0, + _('Auto-response text to send to -request emails.')), + + ('autoresponse_graceperiod', mm_cfg.Number, 3, 0, + _('''Number of days between auto-responses to either the mailing + list or -request/-owner address from the same poster. Set to + zero (or negative) for no grace period (i.e. auto-respond to + every message).''')), + ] + + def _setValue(self, mlist, property, val, doc): + # Handle these specially because we may need to convert to/from + # external $-string representation. + if property in ('autoresponse_postings_text', + 'autoresponse_admin_text', + 'autoresponse_request_text'): + val = self._convertString(mlist, property, ALLOWEDS, val, doc) + if val is None: + # There was a problem, so don't set it + return + GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Bounce.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Bounce.py new file mode 100644 index 0000000..943241a --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Bounce.py @@ -0,0 +1,165 @@ +# Copyright (C) 2001-2004 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from Mailman import mm_cfg +from Mailman.i18n import _ +from Mailman.mm_cfg import days +from Mailman.Gui.GUIBase import GUIBase + + + +class Bounce(GUIBase): + def GetConfigCategory(self): + return 'bounce', _('Bounce processing') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'bounce': + return None + return [ + '\n

      ' + + _("""These policies control the automatic bounce processing system in Mailman. Here's an overview of how it works.""") + + '\n

      ' + + '\n

      ' + + _('When a bounce is received, Mailman tries to extract two pieces of information from the message: the address of the member the message was intended for, and the severity of the problem causing the bounce. The severity can be either hard or soft meaning either a fatal error occurred, or a transient error occurred. When in doubt, a hard severity is used.') + + '\n

      ' + + '\n

      ' + + _('If no member address can be extracted from the bounce, then the bounce is usually discarded. Otherwise, each member is assigned a bounce score and every time we encounter a bounce from this member we increment the score. Hard bounces increment by 1 while soft bounces increment by 0.5. We only increment the bounce score once per day, so even if we receive ten hard bounces from a member per day, their score will increase by only 1 for that day.') + + '\n

      ' + + '\n

      ' + + _("""When a member's bounce score is greater than the bounce score threshold, the subscription is disabled. Once disabled, the member will not receive any postings from the list until their membership is explicitly re-enabled (either by the list administrator or the user). However, they will receive occasional reminders that their membership has been disabled, and these reminders will include information about how to re-enable their membership.""") + + '\n

      ' + + '\n

      ' + + _("""You can control both the number of reminders the member will receive and the frequency with which these reminders are sent.""") + + '\n

      ' + + '\n

      ' + + _('There is one other important configuration variable; after a certain period of time -- during which no bounces from the member are received -- the bounce information is considered stale and discarded. Thus by adjusting this value, and the score threshold, you can control how quickly bouncing members are disabled. You should tune both of these to the frequency and traffic volume of your list.') + + '\n

      ', + + _('Bounce detection sensitivity'), + + ('bounce_processing', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Should Mailman perform automatic bounce processing?'), + _("""By setting this value to No, you disable all + automatic bounce processing for this list, however bounce + messages will still be discarded so that the list administrator + isn't inundated with them.""")), + + ('bounce_score_threshold', mm_cfg.Number, 5, 0, + _("""The maximum member bounce score before the member's + subscription is disabled. This value can be a floating point + number."""), + _("""Each subscriber is assigned a bounce score, as a floating + point number. Whenever Mailman receives a bounce from a list + member, that member's score is incremented. Hard bounces (fatal + errors) increase the score by 1, while soft bounces (temporary + errors) increase the score by 0.5. Only one bounce per day + counts against a member's score, so even if 10 bounces are + received for a member on the same day, their score will increase + by just 1.

      + +

      This variable describes the upper limit for a member's bounce + score, above which they are automatically disabled, but not + removed from the mailing list.""")), + + ('bounce_info_stale_after', mm_cfg.Number, 5, 0, + _("""The number of days after which a member's bounce information + is discarded, if no new bounces have been received in the + interim. This value must be an integer.""")), + + ('bounce_you_are_disabled_warnings', mm_cfg.Number, 5, 0, + _("""How many Your Membership Is Disabled warnings a + disabled member should get before their address is removed from + the mailing list. Set to 0 to immediately remove an address from + the list once their bounce score exceeds the threshold. This + value must be an integer.""")), + + ('bounce_you_are_disabled_warnings_interval', mm_cfg.Number, 5, 0, + _("""The number of days between sending the Your Membership + Is Disabled warnings. This value must be an integer.""")), + + _('Notifications'), + + ('bounce_unrecognized_goes_to_list_owner', mm_cfg.Toggle, + (_('No'), _('Yes')), 0, + _('''Should Mailman send you, the list owner, any bounce messages + that failed to be detected by the bounce processor? Yes + is recommended.'''), + _("""While Mailman's bounce detector is fairly robust, it's + impossible to detect every bounce format in the world. You + should keep this variable set to Yes for two reasons: 1) + If this really is a permanent bounce from one of your members, + you should probably manually remove them from your list, and 2) + you might want to send the message on to the Mailman developers + so that this new format can be added to its known set.

      + +

      If you really can't be bothered, then set this variable to + No and all non-detected bounces will be discarded + without further processing.

      + +

      Note: This setting will also affect all messages sent + to your list's -admin address. This address is deprecated and + should never be used, but some people may still send mail to this + address. If this happens, and this variable is set to + No those messages too will get discarded. You may want + to set up an + autoresponse + message for email to the -owner and -admin address.""")), + + ('bounce_notify_owner_on_disable', mm_cfg.Toggle, + (_('No'), _('Yes')), 0, + _("""Should Mailman notify you, the list owner, when bounces + cause a member's subscription to be disabled?"""), + _("""By setting this value to No, you turn off notification messages that are normally sent to the list owners when a member's delivery is disabled due to excessive bounces. An attempt to notify the member will always be made.""")), + + ('bounce_notify_owner_on_removal', mm_cfg.Toggle, + (_('No'), _('Yes')), 0, + _("""Should Mailman notify you, the list owner, when bounces + cause a member to be unsubscribed?"""), + _("""By setting this value to No, you turn off notification messages that are normally sent to the list owners when a member is unsubscribed due to excessive bounces. An attempt to notify the member will always be made.""")), + + ] + + def _setValue(self, mlist, property, val, doc): + # Do value conversion from web representation to internal + # representation. + try: + if property == 'bounce_processing': + val = int(val) + elif property == 'bounce_score_threshold': + val = float(val) + elif property == 'bounce_info_stale_after': + val = days(int(val)) + elif property == 'bounce_you_are_disabled_warnings': + val = int(val) + elif property == 'bounce_you_are_disabled_warnings_interval': + val = days(int(val)) + elif property == 'bounce_notify_owner_on_disable': + val = int(val) + elif property == 'bounce_notify_owner_on_removal': + val = int(val) + except ValueError: + doc.addError( + _("""Bad value for %(property)s: %(val)s"""), + tag = _('Error: ')) + return + GUIBase._setValue(self, mlist, property, val, doc) + + def getValue(self, mlist, kind, varname, params): + if varname not in ('bounce_info_stale_after', + 'bounce_you_are_disabled_warnings_interval'): + return None + return int(getattr(mlist, varname) / days(1)) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/ContentFilter.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/ContentFilter.py new file mode 100644 index 0000000..83e5c15 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/ContentFilter.py @@ -0,0 +1,162 @@ +# Copyright (C) 2002-2005 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""GUI component managing the content filtering options.""" + +from Mailman import mm_cfg +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + +NL = '\n' + + + +class ContentFilter(GUIBase): + def GetConfigCategory(self): + return 'contentfilter', _('Content filtering') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'contentfilter': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + actions = [_('Discard'), _('Reject'), _('Forward to List Owner')] + if mm_cfg.OWNERS_CAN_PRESERVE_FILTERED_MESSAGES: + actions.append(_('Preserve')) + + return [ + '

      ' + + _('Policies concerning the content of list traffic.') + + '

      ' + + '

      ' + + _('Content filtering works like this: when a message is received by the list and you have enabled content filtering, the individual attachments are first compared to the filter types. If the attachment type matches an entry in the filter types, it is discarded.') + + '

      ' + + '

      ' + + _('Then, if there are pass types defined, any attachment type that does not match a pass type is also discarded. If there are no pass types defined, this check is skipped.') + + '

      ' + + '

      ' + + _('After this initial filtering, any multipart attachments that are empty are removed. If the outer message is left empty after this filtering, then the whole message is discarded.') + + '

      ' + + '

      ' + + _('Then, each multipart/alternative section will be replaced by just the first alternative that is non-empty after filtering if collapse_alternatives is enabled.') + + '

      ' + + '

      ' + + _('Finally, any text/html parts that are left in the message may be converted to text/plain if convert_html_to_plaintext is enabled and the site is configured to allow these conversions.') + + '

      ', + + ('filter_content', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Should Mailman filter the content of list traffic according to the settings below?""")), + + ('filter_mime_types', mm_cfg.Text, (10, WIDTH), 0, + _("""Remove message attachments that have a matching content type."""), + + _("""Use this option to remove each message attachment that matches one of these content types. Each line should contain a string naming a MIME type/subtype, e.g. image/gif. Leave off the subtype to remove all parts with a matching major content type, e.g. image.""") + '

      ' + + + '

      ' + _('Blank lines are ignored.') + '

      ' + + + '

      ' + _('See also pass_mime_types for a content type whitelist.')), + + ('pass_mime_types', mm_cfg.Text, (10, WIDTH), 0, + _("""Remove message attachments that don't have a matching + content type. Leave this field blank to skip this filter + test."""), + + _("""Use this option to remove each message attachment that does + not have a matching content type. Requirements and formats are + exactly like filter_mime_types.

      + +

      Note: if you add entries to this list but don't add + multipart to this list, any messages with attachments + will be rejected by the pass filter.""")), + + ('filter_filename_extensions', mm_cfg.Text, (10, WIDTH), 0, + _("""Remove message attachments that have a matching filename + extension."""),), + + ('pass_filename_extensions', mm_cfg.Text, (10, WIDTH), 0, + _("""Remove message attachments that don't have a matching + filename extension. Leave this field blank to skip this filter + test."""),), + + ('collapse_alternatives', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Should Mailman collapse multipart/alternative to its + first part content?""")), + + ('convert_html_to_plaintext', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Should Mailman convert text/html parts to plain + text? This conversion happens after MIME attachments have been + stripped.""")), + + ('filter_action', mm_cfg.Radio, tuple(actions), 0, + + _("""Action to take when a message matches the content filtering rules."""), + + _("""One of these actions is taken when the message matches one of the content filtering rules, meaning, the top-level content type matches one of the filter_mime_types, or the top-level content type does not match one of the pass_mime_types, or if after filtering the subparts of the message, the message ends up empty.""") + + '

      ' + + '

      ' + + _('Note this action is not taken if after filtering the message still contains content. In that case the message is always forwarded on to the list membership.') + + '

      ' + + '

      ' + + _('When messages are discarded, a log entry is written containing the Message-ID of the discarded message. When messages are rejected or forwarded to the list owner, a reason for the rejection is included in the bounce message to the original author. When messages are preserved, they are saved in a special queue directory on disk for the site administrator to view (and possibly rescue) but otherwise discarded. This last option is only available if enabled by the site administrator.')), + ] + + def _setValue(self, mlist, property, val, doc): + if property in ('filter_mime_types', 'pass_mime_types'): + types = [] + for spectype in [s.strip() for s in val.splitlines()]: + ok = 1 + slashes = spectype.count('/') + if slashes == 0 and not spectype: + ok = 0 + elif slashes == 1: + maintype, subtype = [s.strip().lower() + for s in spectype.split('/')] + if not maintype or not subtype: + ok = 0 + elif slashes > 1: + ok = 0 + if not ok: + doc.addError(_('Bad MIME type ignored: %(spectype)s')) + else: + types.append(spectype.strip().lower()) + if property == 'filter_mime_types': + mlist.filter_mime_types = types + elif property == 'pass_mime_types': + mlist.pass_mime_types = types + elif property in ('filter_filename_extensions', + 'pass_filename_extensions'): + fexts = [] + for ext in [s.strip() for s in val.splitlines()]: + fexts.append(ext.lower()) + if property == 'filter_filename_extensions': + mlist.filter_filename_extensions = fexts + elif property == 'pass_filename_extensions': + mlist.pass_filename_extensions = fexts + else: + GUIBase._setValue(self, mlist, property, val, doc) + + def getValue(self, mlist, kind, property, params): + if property == 'filter_mime_types': + return NL.join(mlist.filter_mime_types) + if property == 'pass_mime_types': + return NL.join(mlist.pass_mime_types) + if property == 'filter_filename_extensions': + return NL.join(mlist.filter_filename_extensions) + if property == 'pass_filename_extensions': + return NL.join(mlist.pass_filename_extensions) + return None diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Digest.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Digest.py new file mode 100644 index 0000000..0c43cc2 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Digest.py @@ -0,0 +1,159 @@ +# Copyright (C) 1998,1999,2000,2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Administrative GUI for digest deliveries.""" + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _ + +# Intra-package import +from Mailman.Gui.GUIBase import GUIBase + +# Common b/w nondigest and digest headers & footers. Personalizations may add +# to this. +ALLOWEDS = ('real_name', 'list_name', 'host_name', 'web_page_url', + 'description', 'info', 'cgiext', '_internal_name', + ) + + + +class Digest(GUIBase): + def GetConfigCategory(self): + return 'digest', _('Digest') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'digest': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + info = [ + '

      ' + _("Batched-delivery digest characteristics.") + '

      ', + + ('digestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1, + _('Can list members choose to receive list traffic ' + 'bunched in digests?')), + + ('digest_is_default', mm_cfg.Radio, + (_('Non-digest'), _('Digest')), 0, + _('Which delivery mode is the default for new users?')), + + ('mime_is_default_digest', mm_cfg.Radio, + (_('Plain'), _('MIME')), 0, + _('When receiving digests, which format is default?')), + + ('digest_size_threshhold', mm_cfg.Number, 3, 0, + _('How big in Kb should a digest be before it gets sent out?')), + # Should offer a 'set to 0' for no size threshhold. + + ('digest_send_periodic', mm_cfg.Radio, (_('No'), _('Yes')), 1, + _('Should a digest be dispatched daily when the size threshold ' + "isn't reached?")), + + ('digest_header', mm_cfg.Text, (4, WIDTH), 0, + _('Header added to every digest'), + _("Text attached (as an initial message, before the table of contents) to the top of digests.

      ") + + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), + + ('digest_footer', mm_cfg.Text, (4, WIDTH), 0, + _('Footer added to every digest'), + _("Text attached (as a final message) to the bottom of digests.

      ") + + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), + + ('digest_volume_frequency', mm_cfg.Radio, + (_('Yearly'), _('Monthly'), _('Quarterly'), + _('Weekly'), _('Daily')), 0, + _('How often should a new digest volume be started?'), + _('''When a new digest volume is started, the volume number is + incremented and the issue number is reset to 1.''')), + + ('_new_volume', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Should Mailman start a new digest volume?'), + _('''Setting this option instructs Mailman to start a new volume + with the next digest sent out.''')), + + ('_send_digest_now', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('''Should Mailman send the next digest right now, if it is not + empty?''')), + ] + +## if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION: +## info.extend([ +## ('digest_personalize', mm_cfg.Toggle, (_('No'), _('Yes')), 1, + +## _('''Should Mailman personalize each digest delivery? +## This is often useful for announce-only lists, but read the details +## section for a discussion of important performance +## issues.'''), + +## _("""Normally, Mailman sends the digest messages to +## the mail server in batches. This is much more efficent +## because it reduces the amount of traffic between Mailman and +## the mail server. + +##

      However, some lists can benefit from a more personalized +## approach. In this case, Mailman crafts a new message for +## each member on the digest delivery list. Turning this on +## adds a few more expansion variables that can be included in +## the message header +## and message footer +## but it may degrade the performance of your site as +## a whole. + +##

      You need to carefully consider whether the trade-off is +## worth it, or whether there are other ways to accomplish what +## you want. You should also carefully monitor your system load +## to make sure it is acceptable. + +##

      These additional substitution variables will be available +## for your headers and footers, when this feature is enabled: + +##

      • user_address - The address of the user, +## coerced to lower case. +##
      • user_delivered_to - The case-preserved address +## that the user is subscribed with. +##
      • user_password - The user's password. +##
      • user_name - The user's full name. +##
      • user_optionsurl - The url to the user's option +## page. +## """)) +## ]) + + return info + + def _setValue(self, mlist, property, val, doc): + # Watch for the special, immediate action attributes + if property == '_new_volume' and val: + mlist.bump_digest_volume() + volume = mlist.volume + number = mlist.next_digest_number + doc.AddItem(_("""The next digest will be sent as volume + %(volume)s, number %(number)s""")) + elif property == '_send_digest_now' and val: + status = mlist.send_digest_now() + if status: + doc.AddItem(_("""A digest has been sent.""")) + else: + doc.AddItem(_("""There was no digest to send.""")) + else: + # Everything else... + if property in ('digest_header', 'digest_footer'): + val = self._convertString(mlist, property, ALLOWEDS, val, doc) + if val is None: + # There was a problem, so don't set it + return + GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/GUIBase.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/GUIBase.py new file mode 100644 index 0000000..e94fd77 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/GUIBase.py @@ -0,0 +1,206 @@ +# Copyright (C) 2002-2004 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Base class for all web GUI components.""" + +import re +from types import TupleType, ListType + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Errors +from Mailman.i18n import _ + +NL = '\n' +BADJOINER = ', ' + + + +class GUIBase: + # Providing a common interface for GUI component form processing. Most + # GUI components won't need to override anything, but some may want to + # override _setValue() to provide some specialized processing for some + # attributes. + def _getValidValue(self, mlist, property, wtype, val): + # Coerce and validate the new value. + # + # Radio buttons and boolean toggles both have integral type + if wtype in (mm_cfg.Radio, mm_cfg.Toggle): + # Let ValueErrors propagate + return int(val) + # String and Text widgets both just return their values verbatim + if wtype in (mm_cfg.String, mm_cfg.Text): + return val + # This widget contains a single email address + if wtype == mm_cfg.Email: + # BAW: We must allow blank values otherwise reply_to_address can't + # be cleared. This is currently the only mm_cfg.Email type widget + # in the interface, so watch out if we ever add any new ones. + if val: + # Let MMBadEmailError and MMHostileAddress propagate + Utils.ValidateEmail(val) + return val + # These widget types contain lists of email addresses, one per line. + # The EmailListEx allows each line to contain either an email address + # or a regular expression + if wtype in (mm_cfg.EmailList, mm_cfg.EmailListEx): + # BAW: value might already be a list, if this is coming from + # config_list input. Sigh. + if isinstance(val, ListType): + return val + addrs = [] + for addr in [s.strip() for s in val.split(NL)]: + # Discard empty lines + if not addr: + continue + try: + # This throws an exception if the address is invalid + Utils.ValidateEmail(addr) + except Errors.EmailAddressError: + # See if this is a context that accepts regular + # expressions, and that the re is legal + if wtype == mm_cfg.EmailListEx and addr.startswith('^'): + try: + re.compile(addr) + except re.error: + raise ValueError + else: + raise + addrs.append(addr) + return addrs + # This is a host name, i.e. verbatim + if wtype == mm_cfg.Host: + return val + # This is a number, either a float or an integer + if wtype == mm_cfg.Number: + num = -1 + try: + num = int(val) + except ValueError: + # Let ValueErrors percolate up + num = float(val) + if num < 0: + return getattr(mlist, property) + return num + # This widget is a select box, i.e. verbatim + if wtype == mm_cfg.Select: + return val + # Checkboxes return a list of the selected items, even if only one is + # selected. + if wtype == mm_cfg.Checkbox: + if isinstance(val, ListType): + return val + return [val] + if wtype == mm_cfg.FileUpload: + return val + if wtype == mm_cfg.Topics: + return val + if wtype == mm_cfg.HeaderFilter: + return val + # Should never get here + assert 0, 'Bad gui widget type: %s' % wtype + + def _setValue(self, mlist, property, val, doc): + # Set the value, or override to take special action on the property + if not property.startswith('_') and getattr(mlist, property) <> val: + setattr(mlist, property, val) + + def _postValidate(self, mlist, doc): + # Validate all the attributes for this category + pass + + def _escape(self, property, value): + value = value.replace('<', '<') + return value + + def handleForm(self, mlist, category, subcat, cgidata, doc): + for item in self.GetConfigInfo(mlist, category, subcat): + # Skip descriptions and legacy non-attributes + if not isinstance(item, TupleType) or len(item) < 5: + continue + # Unpack the gui item description + property, wtype, args, deps, desc = item[0:5] + # BAW: I know this code is a little crufty but I wanted to + # reproduce the semantics of the original code in admin.py as + # closely as possible, for now. We can clean it up later. + # + # The property may be uploadable... + uploadprop = property + '_upload' + if cgidata.has_key(uploadprop) and cgidata[uploadprop].value: + val = cgidata[uploadprop].value + elif not cgidata.has_key(property): + continue + elif isinstance(cgidata[property], ListType): + val = [self._escape(property, x.value) + for x in cgidata[property]] + else: + val = self._escape(property, cgidata[property].value) + # Coerce the value to the expected type, raising exceptions if the + # value is invalid. + try: + val = self._getValidValue(mlist, property, wtype, val) + except ValueError: + doc.addError(_('Invalid value for variable: %(property)s')) + # This is the parent of MMBadEmailError and MMHostileAddress + except Errors.EmailAddressError: + doc.addError( + _('Bad email address for option %(property)s: %(val)s')) + else: + # Set the attribute, which will normally delegate to the mlist + self._setValue(mlist, property, val, doc) + # Do a final sweep once all the attributes have been set. This is how + # we can do cross-attribute assertions + self._postValidate(mlist, doc) + + # Convenience method for handling $-string attributes + def _convertString(self, mlist, property, alloweds, val, doc): + # Is the list using $-strings? + dollarp = getattr(mlist, 'use_dollar_strings', 0) + if dollarp: + ids = Utils.dollar_identifiers(val) + else: + # %-strings + ids = Utils.percent_identifiers(val) + # Here's the list of allowable interpolations + for allowed in alloweds: + if ids.has_key(allowed): + del ids[allowed] + if ids: + # What's left are not allowed + badkeys = ids.keys() + badkeys.sort() + bad = BADJOINER.join(badkeys) + doc.addError(_( + """The following illegal substitution variables were + found in the %(property)s string: %(bad)s

        +

        Your list may not operate properly until you correct this + problem."""), tag=_('Warning: ')) + return val + # Now if we're still using %-strings, do a roundtrip conversion and + # see if the converted value is the same as the new value. If not, + # then they probably left off a trailing `s'. We'll warn them and use + # the corrected string. + if not dollarp: + fixed = Utils.to_percent(Utils.to_dollar(val)) + if fixed <> val: + doc.addError(_( + """Your %(property)s string appeared to + have some correctable problems in its new value. + The fixed value will be used instead. Please + double check that this is what you intended. + """)) + return fixed + return val diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/General.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/General.py new file mode 100644 index 0000000..7f18df4 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/General.py @@ -0,0 +1,452 @@ +# Copyright (C) 2001-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""MailList mixin class managing the general options.""" + +import re + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Errors +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + +OPTIONS = ('hide', 'ack', 'notmetoo', 'nodupes') + + + +class General(GUIBase): + def GetConfigCategory(self): + return 'general', _('Generals') + + def GetConfigInfo(self, mlist, category, subcat): + if category <> 'general': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + # These are for the default_options checkboxes below. + bitfields = {'hide' : mm_cfg.ConcealSubscription, + 'ack' : mm_cfg.AcknowledgePosts, + 'notmetoo' : mm_cfg.DontReceiveOwnPosts, + 'nodupes' : mm_cfg.DontReceiveDuplicates + } + bitdescrs = { + 'hide' : _("Conceal the member's address"), + 'ack' : _("Acknowledge the member's posting"), + 'notmetoo' : _("Do not send a copy of a member's own post"), + 'nodupes' : + _('Filter out duplicate messages to list members (if possible)'), + } + + optvals = [mlist.new_member_options & bitfields[o] for o in OPTIONS] + opttext = [bitdescrs[o] for o in OPTIONS] + + rtn = [ + '

        ' + _('''Fundamental list characteristics, including descriptive + info and basic behaviors.''') + '

        ', + + _('General list personality'), + + ('real_name', mm_cfg.String, WIDTH, 0, + _('The public name of this list (make case-changes only).'), + _('''The capitalization of this name can be changed to make it + presentable in polite company as a proper noun, or to make an + acronym part all upper case, etc. However, the name will be + advertised as the email address (e.g., in subscribe confirmation + notices), so it should not be otherwise altered. (Email + addresses are not case sensitive, but they are sensitive to + almost everything else :-)''')), + + ('owner', mm_cfg.EmailList, (3, WIDTH), 0, + _("""The list administrator email addresses. Multiple + administrator addresses, each on separate line is okay."""), + + _('''There are two ownership roles associated with each mailing + list. The list administrators are the people who have + ultimate control over all parameters of this mailing list. They + are able to change any list configuration variable available + through these administration web pages.

        + +

        The list moderators have more limited permissions; + they are not able to change any list configuration variable, but + they are allowed to tend to pending administration requests, + including approving or rejecting held subscription requests, and + disposing of held postings. Of course, the list + administrators can also tend to pending requests.

        + +

        In order to split the list ownership duties into + administrators and moderators, you must + set a separate moderator password, + and also provide the email + addresses of the list moderators. Note that the field you + are changing here specifies the list administrators.''')), + + ('moderator', mm_cfg.EmailList, (3, WIDTH), 0, + _("""The list moderator email addresses. Multiple + moderator addresses, each on separate line is okay."""), + + _('''There are two ownership roles associated with each mailing + list. The list administrators are the people who have + ultimate control over all parameters of this mailing list. They + are able to change any list configuration variable available + through these administration web pages.

        + +

        The list moderators have more limited permissions; + they are not able to change any list configuration variable, but + they are allowed to tend to pending administration requests, + including approving or rejecting held subscription requests, and + disposing of held postings. Of course, the list + administrators can also tend to pending requests.

        + +

        In order to split the list ownership duties into + administrators and moderators, you must + set a separate moderator password, + and also provide the email addresses of the list moderators in + this section. Note that the field you are changing here + specifies the list moderators.''')), + + ('description', mm_cfg.String, WIDTH, 0, + _('A terse phrase identifying this list.'), + + _('''This description is used when the mailing list is listed with + other mailing lists, or in headers, and so forth. It should + be as succinct as you can get it, while still identifying what + the list is.''')), + + ('info', mm_cfg.Text, (7, WIDTH), 0, + _('''An introductory description - a few paragraphs - about the + list. It will be included, as html, at the top of the listinfo + page. Carriage returns will end a paragraph - see the details + for more info.'''), + _("""The text will be treated as html except that newlines will be translated to <br /> - so you can use links, preformatted text, etc, but don't put in carriage returns except where you mean to separate paragraphs. And review your changes - bad html (like some unterminated HTML constructs) can prevent display of the entire listinfo page.""")), + + ('subject_prefix', mm_cfg.String, WIDTH, 0, + _('Prefix for subject line of list postings.'), + _("""This text will be prepended to subject lines of messages posted to the list, to distinguish mailing list messages in mailbox summaries. Brevity is premium here, it's ok to shorten long mailing list names to something more concise, as long as it still identifies the mailing list.""") + + '

        ' + + '

        ' + + _('You can also add a sequential number by %%d substitution directive. eg.; [listname %%d] -> [listname 123] (listname %%05d) -> (listname 00123).')), + + ('anonymous_list', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Hide the sender of a message, replacing it with the list + address (Removes From, Sender and Reply-To fields)""")), + + _('''Reply-To: header munging'''), + + ('first_strip_reply_to', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('''Should any existing Reply-To: header found in the + original message be stripped? If so, this will be done + regardless of whether an explict Reply-To: header is + added by Mailman or not.''')), + + ('reply_goes_to_list', mm_cfg.Radio, + (_('Poster'), _('This list'), _('Explicit address')), 0, + _('''Where are replies to list messages directed? + Poster is strongly recommended for most mailing + lists.'''), + + # Details for reply_goes_to_list + _("""This option controls what Mailman does to the + Reply-To: header in messages flowing through this + mailing list. When set to Poster, no Reply-To: + header is added by Mailman, although if one is present in the + original message, it is not stripped. Setting this value to + either This list or Explicit address causes + Mailman to insert a specific Reply-To: header in all + messages, overriding the header in the original message if + necessary (Explicit address inserts the value of reply_to_address).

        + +

        There are many reasons not to introduce or override the + Reply-To: header. One is that some posters depend on + their own Reply-To: settings to convey their valid + return address. Another is that modifying Reply-To: + makes it much more difficult to send private replies. See `Reply-To' + Munging Considered Harmful for a general discussion of this + issue. See Reply-To + Munging Considered Useful for a dissenting opinion.

        + +

        Some mailing lists have restricted posting privileges, with a + parallel list devoted to discussions. Examples are `patches' or + `checkin' lists, where software changes are posted by a revision + control system, but discussion about the changes occurs on a + developers mailing list. To support these types of mailing + lists, select Explicit address and set the + Reply-To: address below to point to the parallel + list.""")), + + ('reply_to_address', mm_cfg.Email, WIDTH, 0, + _('Explicit Reply-To: header.'), + # Details for reply_to_address + _("""This is the address set in the Reply-To: header + when the reply_goes_to_list + option is set to Explicit address.

        + +

        There are many reasons not to introduce or override the + Reply-To: header. One is that some posters depend on + their own Reply-To: settings to convey their valid + return address. Another is that modifying Reply-To: + makes it much more difficult to send private replies. See `Reply-To' + Munging Considered Harmful for a general discussion of this + issue. See Reply-To + Munging Considered Useful for a dissenting opinion.

        + +

        Some mailing lists have restricted posting privileges, with a + parallel list devoted to discussions. Examples are `patches' or + `checkin' lists, where software changes are posted by a revision + control system, but discussion about the changes occurs on a + developers mailing list. To support these types of mailing + lists, specify the explicit Reply-To: address here. You + must also specify Explicit address in the + reply_goes_to_list + variable.

        + +

        Note that if the original message contains a + Reply-To: header, it will not be changed.""")), + + _('Umbrella list settings'), + + ('umbrella_list', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('''Send password reminders to, eg, "-owner" address instead of + directly to user.'''), + + _("""Set this to yes when this list is intended to cascade only + to other mailing lists. When set, meta notices like + confirmations and password reminders will be directed to an + address derived from the member\'s address - it will have the + value of "umbrella_member_suffix" appended to the member's + account name.""")), + + ('umbrella_member_suffix', mm_cfg.String, WIDTH, 0, + _('''Suffix for use when this list is an umbrella for other + lists, according to setting of previous "umbrella_list" + setting.'''), + + _("""When "umbrella_list" is set to indicate that this list has + other mailing lists as members, then administrative notices like + confirmations and password reminders need to not be sent to the + member list addresses, but rather to the owner of those member + lists. In that case, the value of this setting is appended to + the member's account name for such notices. `-owner' is the + typical choice. This setting has no effect when "umbrella_list" + is "No".""")), + + _('Notifications'), + + ('send_reminders', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('''Send monthly password reminders?'''), + + _('''Turn this on if you want password reminders to be sent once + per month to your members. Note that members may disable their + own individual password reminders.''')), + + ('welcome_msg', mm_cfg.Text, (4, WIDTH), 0, + _('''List-specific text prepended to new-subscriber welcome + message'''), + + _("""This value, if any, will be added to the front of the new-subscriber welcome message. The rest of the welcome message already describes the important addresses and URLs for the mailing list, so you don't need to include any of that kind of stuff here. This should just contain mission-specific kinds of things, like etiquette policies or team orientation, or that kind of thing.""") + + '

        ' + + '

        ' + + _('Note that this text will be wrapped, according to the following rules:') + + '

        ' + + ''' +
          +
        • ''' + _('Each paragraph is filled so that no line is longer than 70 characters.') + '''
        • +
        • ''' + _('Any line that begins with whitespace is not filled.') + '''
        • +
        • ''' + _('A blank line separates paragraphs.') + '''
        • +
        + +

        '''), + + ('send_welcome_msg', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('Send welcome message to newly subscribed members?'), + _("""Turn this off only if you plan on subscribing people manually + and don't want them to know that you did so. This option is most + useful for transparently migrating lists from some other mailing + list manager to Mailman.""")), + + ('goodbye_msg', mm_cfg.Text, (4, WIDTH), 0, + _('''Text sent to people leaving the list. If empty, no special + text will be added to the unsubscribe message.''')), + + ('send_goodbye_msg', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('Send goodbye message to members when they are unsubscribed?')), + + ('admin_immed_notify', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('''Should the list moderators get immediate notice of new + requests, as well as daily notices about collected ones?'''), + + _('''List moderators (and list administrators) are sent daily + reminders of requests pending approval, like subscriptions to a + moderated list, or postings that are being held for one reason or + another. Setting this option causes notices to be sent + immediately on the arrival of new requests as well.''')), + + ('admin_notify_mchanges', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('''Should administrator get notices of subscribes and + unsubscribes?''')), + + ('respond_to_post_requests', mm_cfg.Radio, + (_('No'), _('Yes')), 0, + _('Send mail to poster when their posting is held for approval?') + ), + + _('Additional settings'), + + ('emergency', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Emergency moderation of all list traffic.'), + _("""When this option is enabled, all list traffic is emergency + moderated, i.e. held for moderation. Turn this option on when + your list is experiencing a flamewar and you want a cooling off + period.""")), + + ('new_member_options', mm_cfg.Checkbox, + (opttext, optvals, 0, OPTIONS), + # The description for new_member_options includes a kludge where + # we add a hidden field so that even when all the checkboxes are + # deselected, the form data will still have a new_member_options + # key (it will always be a list). Otherwise, we'd never be able + # to tell if all were deselected! + 0, _('''Default options for new members joining this list.'''), + + _("""When a new member is subscribed to this list, their initial + set of options is taken from the this variable's setting.""")), + + ('administrivia', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('''(Administrivia filter) Check postings and intercept ones + that seem to be administrative requests?'''), + + _("""Administrivia tests will check postings to see whether it's + really meant as an administrative request (like subscribe, + unsubscribe, etc), and will add it to the the administrative + requests queue, notifying the administrator of the new request, + in the process.""")), + + ('max_message_size', mm_cfg.Number, 7, 0, + _('''Maximum length in kilobytes (KB) of a message body. Use 0 + for no limit.''')), + + ('host_name', mm_cfg.Host, WIDTH, 0, + _('Host name this list prefers for email.'), + + _("""The "host_name" is the preferred name for email to + mailman-related addresses on this host, and generally should be + the mail host's exchanger address, if any. This setting can be + useful for selecting among alternative names of a host that has + multiple addresses.""")), + + ] + + if mm_cfg.ALLOW_RFC2369_OVERRIDES: + rtn.append( + ('include_rfc2369_headers', mm_cfg.Radio, + (_('No'), _('Yes')), 0, + _("""Should messages from this mailing list include the + RFC 2369 + (i.e. List-*) headers? Yes is highly + recommended."""), + + _("""RFC 2369 defines a set of List-* headers that are + normally added to every message sent to the list membership. + These greatly aid end-users who are using standards compliant + mail readers. They should normally always be enabled.

        + +

        However, not all mail readers are standards compliant yet, + and if you have a large number of members who are using + non-compliant mail readers, they may be annoyed at these + headers. You should first try to educate your members as to + why these headers exist, and how to hide them in their mail + clients. As a last resort you can disable these headers, but + this is not recommended (and in fact, your ability to disable + these headers may eventually go away).""")) + ) + # Suppression of List-Post: headers + rtn.append( + ('include_list_post_header', mm_cfg.Radio, + (_('No'), _('Yes')), 0, + _('Should postings include the List-Post: header?'), + _("""The List-Post: header is one of the headers + recommended by + RFC 2369. + However for some announce-only mailing lists, only a + very select group of people are allowed to post to the list; the + general membership is usually not allowed to post. For lists of + this nature, the List-Post: header is misleading. + Select No to disable the inclusion of this header. (This + does not affect the inclusion of the other List-*: + headers.)""")) + ) + + # Discard held messages after this number of days + rtn.append( + ('max_days_to_hold', mm_cfg.Number, 7, 0, + _("""Discard held messages older than this number of days. + Use 0 for no automatic discarding.""")) + ) + + return rtn + + def _setValue(self, mlist, property, val, doc): + if property == 'real_name' and \ + val.lower() <> mlist.internal_name().lower(): + # These values can't differ by other than case + doc.addError(_("""real_name attribute not + changed! It must differ from the list's name by case + only.""")) + elif property == 'new_member_options': + newopts = 0 + for opt in OPTIONS: + bitfield = mm_cfg.OPTINFO[opt] + if opt in val: + newopts |= bitfield + mlist.new_member_options = newopts + elif property == 'subject_prefix': + # Convert any html entities to Unicode + mlist.subject_prefix = Utils.canonstr( + val, mlist.preferred_language) + else: + GUIBase._setValue(self, mlist, property, val, doc) + + def _escape(self, property, value): + # The 'info' property allows HTML, but let's sanitize it to avoid XSS + # exploits. Everything else should be fully escaped. + if property <> 'info': + return GUIBase._escape(self, property, value) + # Sanitize tags but nothing else. Not the best + # solution, but expedient. + return re.sub(r'(?i)<([/]?script.*?)>', r'<\1>', value) + + def _postValidate(self, mlist, doc): + if not mlist.reply_to_address.strip() and \ + mlist.reply_goes_to_list == 2: + # You can't go to an explicit address that is blank + doc.addError(_("""You cannot add a Reply-To: to an explicit + address if that address is blank. Resetting these values.""")) + mlist.reply_to_address = '' + mlist.reply_goes_to_list = 0 + + def getValue(self, mlist, kind, varname, params): + if varname <> 'subject_prefix': + return None + # The subject_prefix may be Unicode + return Utils.uncanonstr(mlist.subject_prefix, mlist.preferred_language) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Language.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Language.py new file mode 100644 index 0000000..2a8e042 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Language.py @@ -0,0 +1,122 @@ +# Copyright (C) 2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""MailList mixin class managing the language options. +""" + +import codecs + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import i18n +from Mailman.Logging.Syslog import syslog +from Mailman.Gui.GUIBase import GUIBase + +_ = i18n._ + + + +class Language(GUIBase): + def GetConfigCategory(self): + return 'language', _('Languages') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'language': + return None + + # Set things up for the language choices + langs = mlist.GetAvailableLanguages() + langnames = [_(Utils.GetLanguageDescr(L)) for L in langs] + try: + langi = langs.index(mlist.preferred_language) + except ValueError: + # Someone must have deleted the list's preferred language. Could + # be other trouble lurking! + langi = 0 + + # Only allow the admin to choose a language if the system has a + # charset for it. I think this is the best way to test for that. + def checkcodec(charset): + try: + codecs.lookup(charset) + return 1 + except LookupError: + return 0 + + all = [key for key in mm_cfg.LC_DESCRIPTIONS.keys() + if checkcodec(Utils.GetCharSet(key))] + all.sort() + checked = [L in langs for L in all] + allnames = [_(Utils.GetLanguageDescr(L)) for L in all] + + return [ + '

        ' + _('Natural language (internationalization) options.') + '

        ', + + ('preferred_language', mm_cfg.Select, + (langs, langnames, langi), + 0, + _('Default language for this list.'), + _('''This is the default natural language for this mailing list. + If more than one + language is supported then users will be able to select their + own preferences for when they interact with the list. All other + interactions will be conducted in the default language. This + applies to both web-based and email-based messages, but not to + email posted by list members.''')), + + ('available_languages', mm_cfg.Checkbox, + (allnames, checked, 0, all), 0, + _('Languages supported by this list.'), + + _('''These are all the natural languages supported by this list. + Note that the + default + language must be included.''')), + + ('encode_ascii_prefixes', mm_cfg.Radio, + (_('Never'), _('Always'), _('As needed')), 0, + _("""Encode the + subject + prefix even when it consists of only ASCII characters?"""), + + _("""If your mailing list's default language uses a non-ASCII + character set and the prefix contains non-ASCII characters, the + prefix will always be encoded according to the relevant + standards. However, if your prefix contains only ASCII + characters, you may want to set this option to Never to + disable prefix encoding. This can make the subject headers + slightly more readable for users with mail readers that don't + properly handle non-ASCII encodings.

        + +

        Note however, that if your mailing list receives both encoded + and unencoded subject headers, you might want to choose As + needed. Using this setting, Mailman will not encode ASCII + prefixes when the rest of the header contains only ASCII + characters, but if the original header contains non-ASCII + characters, it will encode the prefix. This avoids an ambiguity + in the standards which could cause some mail readers to display + extra, or missing spaces between the prefix and the original + header.""")), + + ] + + def _setValue(self, mlist, property, val, doc): + # If we're changing the list's preferred language, change the I18N + # context as well + if property == 'preferred_language': + i18n.set_language(val) + doc.set_language(val) + GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Membership.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Membership.py new file mode 100644 index 0000000..a061ad3 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Membership.py @@ -0,0 +1,34 @@ +# Copyright (C) 2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""MailList mixin class managing the membership pseudo-options. +""" + +from Mailman.i18n import _ + + + +class Membership: + def GetConfigCategory(self): + return 'members', _('Membership...') + + def GetConfigSubCategories(self, category): + if category == 'members': + return [('list', _('Membership List')), + ('add', _('Mass Subscription')), + ('remove', _('Mass Removal')), + ] + return None diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/NonDigest.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/NonDigest.py new file mode 100644 index 0000000..886428a --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/NonDigest.py @@ -0,0 +1,155 @@ +# Copyright (C) 2001-2003 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +""" GUI component for managing the non-digest delivery options. +""" + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + +from Mailman.Gui.Digest import ALLOWEDS +PERSONALIZED_ALLOWEDS = ('user_address', 'user_delivered_to', 'user_password', + 'user_name', 'user_optionsurl', + ) + + + +class NonDigest(GUIBase): + def GetConfigCategory(self): + return 'nondigest', _('Non-digest') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'nondigest': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + info = [ + '

        ' + _("Policies concerning immediately delivered list traffic.") + '

        ', + + ('nondigestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1, + _("""Can subscribers choose to receive mail immediately, rather + than in batched digests?""")), + ] + + if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION: + info.extend([ + ('personalize', mm_cfg.Radio, + (_('No'), _('Yes'), _('Full Personalization')), 1, + + _('''Should Mailman personalize each non-digest delivery? + This is often useful for announce-only lists, but read the details + section for a discussion of important performance + issues.'''), + + _("""Normally, Mailman sends the regular delivery messages to + the mail server in batches. This is much more efficent + because it reduces the amount of traffic between Mailman and + the mail server.

        + +

        However, some lists can benefit from a more personalized + approach. In this case, Mailman crafts a new message for + each member on the regular delivery list. Turning this + feature on may degrade the performance of your site, so you + need to carefully consider whether the trade-off is worth it, + or whether there are other ways to accomplish what you want. + You should also carefully monitor your system load to make + sure it is acceptable.

        + +

        Select No to disable personalization and send + messages to the members in batches. Select Yes to + personalize deliveries and allow additional substitution + variables in message headers and footers (see below). In + addition, by selecting Full Personalization, the + To header of posted messages will be modified to + include the member's address instead of the list's posting + address.

        + +

        When personalization is enabled, a few more expansion + variables that can be included in the message header and + message footer.

        + +

        These additional substitution variables will be available + for your headers and footers, when this feature is enabled:

        + +
          +
        • user_address - The address of the user, + coerced to lower case.
        • +
        • user_delivered_to - The case-preserved address + that the user is subscribed with.
        • +
        • user_password - The user's password.
        • +
        • user_name - The user's full name.
        • +
        • user_optionsurl - The url to the user's option + page.
        • +
        + +

        """))]) + # BAW: for very dumb reasons, we want the `personalize' attribute to + # show up before the msg_header and msg_footer attrs, otherwise we'll + # get a bogus warning if the header/footer contains a personalization + # substitution variable, and we're transitioning from no + # personalization to personalization enabled. + headfoot = Utils.maketext('headfoot.html', mlist=mlist, raw=1) + if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION: + extra = _("""\ +

        When personalization is enabled + for this list, additional substitution variables are allowed in your headers + and footers:

        + +
          +
        • user_address - The address of the user, coerced to lower case.
        • +
        • user_delivered_to - The case-preserved address that the user is subscribed with.
        • +
        • user_password - The user's password.
        • +
        • user_name - The user's full name.
        • +
        • user_optionsurl - The url to the user's option page.
        • +
        + +

        """) + else: + extra = '' + + info.extend([('msg_header', mm_cfg.Text, (10, WIDTH), 0, + _('Header added to mail sent to regular list members'), + _('''Text prepended to the top of every immediately-delivery message.

        ''') + headfoot + extra), + + ('msg_footer', mm_cfg.Text, (10, WIDTH), 0, + _('Footer added to mail sent to regular list members'), + _('''Text appended to the bottom of every immediately-delivery message.

        ''') + headfoot + extra), + ]) + + info.extend([ + ('scrub_nondigest', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Scrub attachments of regular delivery message?'), + _('''When you scrub attachments, they are stored in archive + area and links are made in the message so that the member can + access via web browser. If you want the attachments totally + disappear, you can use content filter options.''')), + ]) + return info + + def _setValue(self, mlist, property, val, doc): + alloweds = list(ALLOWEDS) + if mlist.personalize: + alloweds.extend(PERSONALIZED_ALLOWEDS) + if property in ('msg_header', 'msg_footer'): + val = self._convertString(mlist, property, alloweds, val, doc) + if val is None: + # There was a problem, so don't set it + return + GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Passwords.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Passwords.py new file mode 100644 index 0000000..8ea9bcf --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Passwords.py @@ -0,0 +1,31 @@ +# Copyright (C) 2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""MailList mixin class managing the password pseudo-options. +""" + +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + + + +class Passwords(GUIBase): + def GetConfigCategory(self): + return 'passwords', _('Passwords') + + def handleForm(self, mlist, category, subcat, cgidata, doc): + # Nothing more needs to be done + pass diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Privacy.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Privacy.py new file mode 100644 index 0000000..a55a73f --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Privacy.py @@ -0,0 +1,510 @@ +# Copyright (C) 2001-2005 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""MailList mixin class managing the privacy options.""" + +import re + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +class Privacy(GUIBase): + def GetConfigCategory(self): + return 'privacy', _('Privacy...') + + def GetConfigSubCategories(self, category): + if category == 'privacy': + return [('subscribing', _('Subscription rules')), + ('sender', _('Sender filters')), + ('recipient', _('Recipient filters')), + ('spam', _('Spam filters')), + ] + return None + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'privacy': + return None + # Pre-calculate some stuff. Technically, we shouldn't do the + # sub_cfentry calculation here, but it's too ugly to indent it any + # further, and besides, that'll mess up i18n catalogs. + WIDTH = mm_cfg.TEXTFIELDWIDTH + if mm_cfg.ALLOW_OPEN_SUBSCRIBE: + sub_cfentry = ('subscribe_policy', mm_cfg.Radio, + # choices + (_('None'), + _('Confirm'), + _('Require approval'), + _('Confirm and approve')), + 0, + _('What steps are required for subscription?'), + _('Available options') + ':' + + ''' +

        + +
          +
        • ''' + _('None - no verification steps (Not Recommended)') + '''
        • +
        • ''' + _('Confirm (*) - email confirmation step required') + '''
        • +
        • ''' + _('Require approval - require list administrator approval for subscriptions') + '''
        • +
        • ''' + _('Confirm and approve - both confirm and approve') + '''
        • + +
        + +

        ''' + _('(*) when someone requests a subscription, Mailman sends them a notice with a unique subscription request number that they must reply to in order to subscribe. This prevents mischievous (or malicious) people from creating subscriptions for others without their consent.')) + else: + sub_cfentry = ('subscribe_policy', mm_cfg.Radio, + # choices + (_('Confirm'), + _('Require approval'), + _('Confirm and approve')), + 1, + _('What steps are required for subscription?'), + _('Available options') + ':' + + ''' +

        + +
          +
        • ''' + _('None - no verification steps (Not Recommended)') + '''
        • +
        • ''' + _('Confirm (*) - email confirmation step required') + '''
        • +
        • ''' + _('Require approval - require list administrator approval for subscriptions') + '''
        • +
        • ''' + _('Confirm and approve - both confirm and approve') + '''
        • + +
        + +

        ''' + _('(*) when someone requests a subscription, Mailman sends them a notice with a unique subscription request number that they must reply to in order to subscribe. This prevents mischievous (or malicious) people from creating subscriptions for others without their consent.')) + + # some helpful values + admin = mlist.GetScriptURL('admin') + + subscribing_rtn = [ + '

        ' + _("""This section allows you to configure subscription and membership exposure policy. You can also control whether this list is public or not. See also the Archival Options section for separate archive-related privacy settings.""") + '

        ', + + _('Subscribing'), + ('advertised', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('''Advertise this list when people ask what lists are on this + machine?''')), + + sub_cfentry, + + ('unsubscribe_policy', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Is the list moderator's approval required for unsubscription + requests? (No is recommended)"""), + + _("""When members want to leave a list, they will make an + unsubscription request, either via the web or via email. + Normally it is best for you to allow open unsubscriptions so that + users can easily remove themselves from mailing lists (they get + really upset if they can't get off lists!).

        + +

        For some lists though, you may want to impose moderator + approval before an unsubscription request is processed. Examples + of such lists include a corporate mailing list that all employees + are required to be members of.""")), + + _('Ban list'), + ('ban_list', mm_cfg.EmailListEx, (10, WIDTH), 1, + _("""List of addresses which are banned from membership in this + mailing list."""), + + _("""Addresses in this list are banned outright from subscribing + to this mailing list, with no further moderation required. Add + addresses one per line; start the line with a ^ character to + designate a regular expression match.""")), + + _("Membership exposure"), + ('private_roster', mm_cfg.Radio, + (_('Anyone'), _('List members'), _('List admin only')), 0, + _('Who can view subscription list?'), + + _('''When set, the list of subscribers is protected by member or + admin password authentication.''')), + + ('obscure_addresses', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Show member addresses so they're not directly recognizable + as email addresses?"""), + _("""Setting this option causes member email addresses to be + transformed when they are presented on list web pages (both in + text and as links), so they're not trivially recognizable as + email addresses. The intention is to prevent the addresses + from being snarfed up by automated web scanners for use by + spammers.""")), + ] + + adminurl = mlist.GetScriptURL('admin', absolute=1) + sender_rtn = [ + _("""

        When a message is posted to the list, a series of + moderation steps are take to decide whether the a moderator must + first approve the message or not. This section contains the + controls for moderation of both member and non-member postings.

        + +

        Member postings are held for moderation if their + moderation flag is turned on. You can control whether + member postings are moderated by default or not.

        + +

        Non-member postings can be automatically + accepted, + held for + moderation, + rejected (bounced), or + discarded, + either individually or as a group. Any + posting from a non-member who is not explicitly accepted, + rejected, or discarded, will have their posting filtered by the + general + non-member rules.

        + +

        In the text boxes below, add one address per line; start the + line with a ^ character to designate a Python regular expression. When entering backslashes, do so + as if you were using Python raw strings (i.e. you generally just + use a single backslash).

        + +

        Note that non-regexp matches are always done first.

        """), + + _('Member filters'), + + ('default_member_moderation', mm_cfg.Radio, (_('No'), _('Yes')), + 0, _('By default, should new list member postings be moderated?'), + + _("""Each list member has a moderation flag which says + whether messages from the list member can be posted directly to + the list, or must first be approved by the list moderator. When + the moderation flag is turned on, list member postings must be + approved first. You, the list administrator can decide whether a + specific individual's postings will be moderated or not.

        + +

        When a new member is subscribed, their initial moderation flag + takes its value from this option. Turn this option off to accept + member postings by default. Turn this option on to, by default, + moderate member postings first. You can always manually set an + individual member's moderation bit by using the + membership management + screens.""")), + + ('member_moderation_action', mm_cfg.Radio, + (_('Hold'), _('Reject'), _('Discard')), 0, + _("""Action to take when a moderated member posts to the + list."""), + _('Available options') + ':

        ' + + ''' +
          +
        • ''' + _('Hold') + ''' -- ''' + _('this holds the message for approval by the list moderators.') + '''
        • +
        • ''' + _('Reject') + ''' -- ''' + _('''this automatically rejects the message by sending a bounce notice to the post's author. The text of the bounce notice can be configured by you.''') + '''
        • +
        • ''' + _('Discard') + ''' -- ''' + _('''this simply discards the message, with no notice sent to the post's author.''') + '''
        • +
        + +

        + '''), + + ('member_moderation_notice', mm_cfg.Text, (10, WIDTH), 1, + _("""Text to include in any + rejection notice to + be sent to moderated members who post to this list.""")), + + _('Non-member filters'), + + ('accept_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, + _("""List of non-member addresses whose postings should be + automatically accepted."""), + + _("""Postings from any of these non-members will be automatically accepted with no further moderation applied.""") + + '

        ' + + '

        ' + + _('Add member addresses one per line; start the line with a ^ character to designate a regular expression match.')), + + ('hold_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, + _("""List of non-member addresses whose postings will be + immediately held for moderation."""), + + _("""Postings from any of these non-members will be immediately and automatically held for moderation by the list moderators. The sender will receive a notification message which will allow them to cancel their held message.""") + + '

        ' + + '

        ' + + _('Add member addresses one per line; start the line with a ^ character to designate a regular expression match.')), + + ('reject_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, + _("""List of non-member addresses whose postings will be + automatically rejected."""), + + _("""Postings from any of these non-members will be automatically rejected. In other words, their messages will be bounced back to the sender with a notification of automatic rejection. This option is not appropriate for known spam senders; their messages should be automatically discarded.""") + + '

        ' + + '

        ' + + _('Add member addresses one per line; start the line with a ^ character to designate a regular expression match.')), + + ('discard_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, + _("""List of non-member addresses whose postings will be + automatically discarded."""), + + _("""Postings from any of these non-members will be automatically discarded. That is, the message will be thrown away with no further processing or notification. The sender will not receive a notification or a bounce, however the list moderators can optionally receive copies of auto-discarded messages.""") + + '

        ' + + '

        ' + + _('Add member addresses one per line; start the line with a ^ character to designate a regular expression match.')), + + ('generic_nonmember_action', mm_cfg.Radio, + (_('Accept'), _('Hold'), _('Reject'), _('Discard')), 0, + _("""Action to take for postings from non-members for which no + explicit action is defined."""), + + _("""When a post from a non-member is received, the message's + sender is matched against the list of explicitly + accepted, + held, + rejected (bounced), and + discarded addresses. If no match is found, then this action + is taken.""")), + + ('forward_auto_discards', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Should messages from non-members, which are automatically + discarded, be forwarded to the list moderator?""")), + + ('nonmember_rejection_notice', mm_cfg.Text, (10, WIDTH), 1, + _("""Text to include in any rejection notice to be sent to + non-members who post to this list. This notice can include + the list's owner address by %%(listowner)s and replaces the + internally crafted default message.""")), + + ] + + recip_rtn = [ + '

        ' + + _("""This section allows you to configure various filters based on the recipient of the message.""") + + '

        ', + + _('Recipient filters'), + + ('require_explicit_destination', mm_cfg.Radio, + (_('No'), _('Yes')), 0, + _("""Must posts have list named in destination (to, cc) field + (or be among the acceptable alias names, specified below)?"""), + + _("""Many (in fact, most) spams do not explicitly name their myriad destinations in the explicit destination addresses - in fact often the To: field has a totally bogus address for obfuscation. The constraint applies only to the stuff in the address before the '@' sign, but still catches all such spams.""") + + '

        ' + + '

        ' + + _('The cost is that the list will not accept unhindered any postings relayed from other addresses, unless:') + '''

        + +
          +
        1. ''' + _('The relaying address has the same name, or') + '''
        2. +
        3. ''' + _('The relaying address name is included on the options that specifies acceptable aliases for the list.') + '''
        4. + +
        +

        + '''), + + ('acceptable_aliases', mm_cfg.Text, (4, WIDTH), 0, + _("""Alias names (regexps) which qualify as explicit to or cc + destination names for this list."""), + + _("""Alternate addresses that are acceptable when + `require_explicit_destination' is enabled. This option takes a + list of regular expressions, one per line, which is matched + against every recipient address in the message. The matching is + performed with Python's re.match() function, meaning they are + anchored to the start of the string.

        + +

        For backwards compatibility with Mailman 1.1, if the regexp + does not contain an `@', then the pattern is matched against just + the local part of the recipient address. If that match fails, or + if the pattern does contain an `@', then the pattern is matched + against the entire recipient address.

        + +

        Matching against the local part is deprecated; in a future + release, the pattern will always be matched against the entire + recipient address.""")), + + ('max_num_recipients', mm_cfg.Number, 5, 0, + _('Ceiling on acceptable number of recipients for a posting.'), + + _('''If a posting has this number, or more, of recipients, it is + held for admin approval. Use 0 for no ceiling.''')), + ] + + spam_rtn = [ + '

        ' + + _('''This section allows you to configure various anti-spam filters posting filters, which can help reduce the amount of spam your list members end up receiving.''') + + '

        ', + + _('Header filters'), + + ('header_filter_rules', mm_cfg.HeaderFilter, 0, 0, + _('Filter rules to match against the headers of a message.'), + + _("""Each header filter rule has two parts, a list of regular + expressions, one per line, and an action to take. Mailman + matches the message's headers against every regular expression in + the rule and if any match, the message is rejected, held, or + discarded based on the action you specify. Use Defer to + temporarily disable a rule.

        + +

        You can have more than one filter rule for your list. In that + case, each rule is matched in turn, with processing stopped after + the first match.

        + +

        Note that headers are collected from all the attachments + (except for the mailman administrivia message) and + matched against the regular expressions. With this feature, + you can effectively sort out messages with dangerous file + types or file name extensions.""")), + + _('Legacy anti-spam filters'), + + ('bounce_matching_headers', mm_cfg.Text, (6, WIDTH), 0, + _('Hold posts with header value matching a specified regexp.'), + _("""Use this option to prohibit posts according to specific header values. The target value is a regular-expression for matching against the specified header. The match is done disregarding letter case. Lines beginning with '#' are ignored as comments.""") + + '

        ' + + '

        ' + + _('For example:') + + '

        ' + + ''' +
        to: .*@public.com
        +

        + ''' + + _("""says to hold all postings with a To: mail header containing '@public.com' anywhere among the addresses.""") + + '

        ' + '

        ' + + _('Note that leading whitespace is trimmed from the regexp. This can be circumvented in a number of ways, e.g. by escaping or bracketing it.')), + ] + + if subcat == 'sender': + return sender_rtn + elif subcat == 'recipient': + return recip_rtn + elif subcat == 'spam': + return spam_rtn + else: + return subscribing_rtn + + def _setValue(self, mlist, property, val, doc): + # Ignore any hdrfilter_* form variables + if property.startswith('hdrfilter_'): + return + # For subscribe_policy when ALLOW_OPEN_SUBSCRIBE is true, we need to + # add one to the value because the page didn't present an open list as + # an option. + if property == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE: + val += 1 + setattr(mlist, property, val) + + # We need to handle the header_filter_rules widgets specially, but + # everything else can be done by the base class's handleForm() method. + # However, to do this we need an awful hack. _setValue() and + # _getValidValue() will essentially ignore any hdrfilter_* form variables. + # TK: we should call this function only in subcat == 'spam' + def _handleForm(self, mlist, category, subcat, cgidata, doc): + # TK: If there is no hdrfilter_* in cgidata, we should not touch + # the header filter rules. + if not cgidata.has_key('hdrfilter_rebox_01'): + return + # First deal with + rules = [] + # We start i at 1 and keep going until we no longer find items keyed + # with the marked tags. + i = 1 + downi = None + while True: + deltag = 'hdrfilter_delete_%02d' % i + reboxtag = 'hdrfilter_rebox_%02d' % i + actiontag = 'hdrfilter_action_%02d' % i + wheretag = 'hdrfilter_where_%02d' % i + addtag = 'hdrfilter_add_%02d' % i + newtag = 'hdrfilter_new_%02d' % i + uptag = 'hdrfilter_up_%02d' % i + downtag = 'hdrfilter_down_%02d' % i + i += 1 + # Was this a delete? If so, we can just ignore this entry + if cgidata.has_key(deltag): + continue + # Get the data for the current box + pattern = cgidata.getvalue(reboxtag) + try: + action = int(cgidata.getvalue(actiontag)) + # We'll get a TypeError when the actiontag is missing and the + # .getvalue() call returns None. + except (ValueError, TypeError): + action = mm_cfg.DEFER + if pattern is None: + # We came to the end of the boxes + break + if cgidata.has_key(newtag) and not pattern: + # This new entry is incomplete. + if i == 2: + # OK it is the first. + continue + doc.addError(_("""Header filter rules require a pattern. + Incomplete filter rules will be ignored.""")) + continue + # Make sure the pattern was a legal regular expression + try: + re.compile(pattern) + except (re.error, TypeError): + safepattern = Utils.websafe(pattern) + doc.addError(_("""The header filter rule pattern + '%(safepattern)s' is not a legal regular expression. This + rule will be ignored.""")) + continue + # Was this an add item? + if cgidata.has_key(addtag): + # Where should the new one be added? + where = cgidata.getvalue(wheretag) + if where == 'before': + # Add a new empty rule box before the current one + rules.append(('', mm_cfg.DEFER, True)) + rules.append((pattern, action, False)) + # Default is to add it after... + else: + rules.append((pattern, action, False)) + rules.append(('', mm_cfg.DEFER, True)) + # Was this an up movement? + elif cgidata.has_key(uptag): + # As long as this one isn't the first rule, move it up + if rules: + rules.insert(-1, (pattern, action, False)) + else: + rules.append((pattern, action, False)) + # Was this the down movement? + elif cgidata.has_key(downtag): + downi = i - 2 + rules.append((pattern, action, False)) + # Otherwise, just retain this one in the list + else: + rules.append((pattern, action, False)) + # Move any down button filter rule + if downi is not None: + rule = rules[downi] + del rules[downi] + rules.insert(downi+1, rule) + mlist.header_filter_rules = rules + + def handleForm(self, mlist, category, subcat, cgidata, doc): + if subcat == 'spam': + self._handleForm(mlist, category, subcat, cgidata, doc) + # Everything else is dealt with by the base handler + GUIBase.handleForm(self, mlist, category, subcat, cgidata, doc) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Topics.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Topics.py new file mode 100644 index 0000000..eb1d4ce --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Topics.py @@ -0,0 +1,156 @@ +# Copyright (C) 2001-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +import re + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _ +from Mailman.Logging.Syslog import syslog +from Mailman.Gui.GUIBase import GUIBase + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +class Topics(GUIBase): + def GetConfigCategory(self): + return 'topics', _('Topics') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'topics': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + return [ + '

        ' + + _('List topic keywords.') + + '

        ', + + ('topics_enabled', mm_cfg.Radio, (_('Disabled'), _('Enabled')), 0, + _('''Should the topic filter be enabled or disabled?'''), + + _("""The topic filter categorizes each incoming email message according to regular expression filters you specify below. If the message's Subject: or Keywords: header contains a match against a topic filter, the message is logically placed into a topic bucket. Each user can then choose to only receive messages from the mailing list for a particular topic bucket (or buckets). Any message not categorized in a topic bucket registered with the user is not delivered to the list.""") + + '

        ' + + '

        ' + + _('Note that this feature only works with regular delivery, not digest delivery.') + + '

        ' + + '

        ' + + _('The body of the message can also be optionally scanned for Subject: and Keywords: headers, as specified by the topics_bodylines_limit configuration variable.')), + + ('topics_bodylines_limit', mm_cfg.Number, 5, 0, + _('How many body lines should the topic matcher scan?'), + + _("""The topic matcher will scan this many lines of the message + body looking for topic keyword matches. Body scanning stops when + either this many lines have been looked at, or a non-header-like + body line is encountered. By setting this value to zero, no body + lines will be scanned (i.e. only the Keywords: and + Subject: headers will be scanned). By setting this + value to a negative number, then all body lines will be scanned + until a non-header-like line is encountered. + """)), + + ('topics', mm_cfg.Topics, 0, 0, + _('Topic keywords, one per line, to match against each message.'), + + _("""Each topic keyword is actually a regular expression, which is + matched against certain parts of a mail message, specifically the + Keywords: and Subject: message headers. + Note that the first few lines of the body of the message can also + contain a Keywords: and Subject: + "header" on which matching is also performed.""")), + + ] + + def handleForm(self, mlist, category, subcat, cgidata, doc): + # MAS: Did we come from the authentication page? + if not cgidata.has_key('topic_box_01'): + return + topics = [] + # We start i at 1 and keep going until we no longer find items keyed + # with the marked tags. + i = 1 + while True: + deltag = 'topic_delete_%02d' % i + boxtag = 'topic_box_%02d' % i + reboxtag = 'topic_rebox_%02d' % i + desctag = 'topic_desc_%02d' % i + wheretag = 'topic_where_%02d' % i + addtag = 'topic_add_%02d' % i + newtag = 'topic_new_%02d' % i + i += 1 + # Was this a delete? If so, we can just ignore this entry + if cgidata.has_key(deltag): + continue + # Get the data for the current box + name = cgidata.getvalue(boxtag) + pattern = cgidata.getvalue(reboxtag) + desc = cgidata.getvalue(desctag) + if name is None: + # We came to the end of the boxes + break + if cgidata.has_key(newtag) and (not name or not pattern): + # This new entry is incomplete. + doc.addError(_("""Topic specifications require both a name and + a pattern. Incomplete topics will be ignored.""")) + continue + # Make sure the pattern was a legal regular expression + name = Utils.websafe(name) + try: + re.compile(pattern) + except (re.error, TypeError): + safepattern = Utils.websafe(pattern) + doc.addError(_("""The topic pattern '%(safepattern)s' is not a + legal regular expression. It will be discarded.""")) + continue + # Was this an add item? + if cgidata.has_key(addtag): + # Where should the new one be added? + where = cgidata.getvalue(wheretag) + if where == 'before': + # Add a new empty topics box before the current one + topics.append(('', '', '', True)) + topics.append((name, pattern, desc, False)) + # Default is to add it after... + else: + topics.append((name, pattern, desc, False)) + topics.append(('', '', '', True)) + # Otherwise, just retain this one in the list + else: + topics.append((name, pattern, desc, False)) + # Add these topics to the mailing list object, and deal with other + # options. + mlist.topics = topics + try: + mlist.topics_enabled = int(cgidata.getvalue( + 'topics_enabled', + mlist.topics_enabled)) + except ValueError: + # BAW: should really print a warning + pass + try: + mlist.topics_bodylines_limit = int(cgidata.getvalue( + 'topics_bodylines_limit', + mlist.topics_bodylines_limit)) + except ValueError: + # BAW: should really print a warning + pass diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Usenet.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Usenet.py new file mode 100644 index 0000000..2924d81 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/Usenet.py @@ -0,0 +1,141 @@ +# Copyright (C) 2001-2003 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from Mailman import mm_cfg +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + + + +class Usenet(GUIBase): + def GetConfigCategory(self): + return 'gateway', _('Mail<->News gateways') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'gateway': + return None + + WIDTH = mm_cfg.TEXTFIELDWIDTH + VERTICAL = 1 + + return [ + '

        ' + + _('Mail-to-News and News-to-Mail gateway services.') + + '

        ', + + _('News server settings'), + + ('nntp_host', mm_cfg.String, WIDTH, 0, + _('The hostname of the machine your news server is running on.'), + _('''This value may be either the name of your news server, or + optionally of the format name:port, where port is a port number.

        + +

        The news server is not part of Mailman proper. You have to + already have access to an NNTP server, and that NNTP server must + recognize the machine this mailing list runs on as a machine + capable of reading and posting news.''')), + + ('linked_newsgroup', mm_cfg.String, WIDTH, 0, + _('The name of the Usenet group to gateway to and/or from.')), + + ('gateway_to_news', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('''Should new posts to the mailing list be sent to the + newsgroup?''')), + + ('gateway_to_mail', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('''Should new posts to the newsgroup be sent to the mailing + list?''')), + + _('Forwarding options'), + + ('news_moderation', mm_cfg.Radio, + (_('None'), _('Open list, moderated group'), _('Moderated')), + VERTICAL, + + _("""The moderation policy of the newsgroup."""), + + _("""This setting determines the moderation policy of the + newsgroup and its interaction with the moderation policy of the + mailing list. This only applies to the newsgroup that you are + gatewaying to, so if you are only gatewaying from + Usenet, or the newsgroup you are gatewaying to is not moderated, + set this option to None.

        + +

        If the newsgroup is moderated, you can set this mailing list + up to be the moderation address for the newsgroup. By selecting + Moderated, an additional posting hold will be placed in + the approval process. All messages posted to the mailing list + will have to be approved before being sent on to the newsgroup, + or to the mailing list membership.

        + +

        Note that if the message has an Approved header + with the list's administrative password in it, this hold test + will be bypassed, allowing privileged posters to send messages + directly to the list and the newsgroup.

        + +

        Finally, if the newsgroup is moderated, but you want to have + an open posting policy anyway, you should select Open list, + moderated group. The effect of this is to use the normal + Mailman moderation facilities, but to add an Approved + header to all messages that are gatewayed to Usenet.""")), + + ('news_prefix_subject_too', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Prefix Subject: headers on postings gated to news?'), + _("""Mailman prefixes Subject: headers with + text you can + customize and normally, this prefix shows up in messages + gatewayed to Usenet. You can set this option to No to + disable the prefix on gated messages. Of course, if you turn off + normal Subject: prefixes, they won't be prefixed for + gated messages either.""")), + + _('Mass catch up'), + + ('_mass_catchup', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Should Mailman perform a catchup on the newsgroup?'), + _('''When you tell Mailman to perform a catchup on the newsgroup, + this means that you want to start gating messages to the mailing + list with the next new message found. All earlier messages on + the newsgroup will be ignored. This is as if you were reading + the newsgroup yourself, and you marked all current messages as + read. By catching up, your mailing list members will + not see any of the earlier messages.''')), + + ] + + def _setValue(self, mlist, property, val, doc): + # Watch for the special, immediate action attributes + if property == '_mass_catchup' and val: + mlist.usenet_watermark = None + doc.AddItem(_('Mass catchup completed')) + else: + GUIBase._setValue(self, mlist, property, val, doc) + + def _postValidate(self, mlist, doc): + # Make sure that if we're gating, that the newsgroups and host + # information are not blank. + if mlist.gateway_to_news or mlist.gateway_to_mail: + # BAW: It's too expensive and annoying to ensure that both the + # host is valid and that the newsgroup is a valid n.g. on the + # server. This should be good enough. + if not mlist.nntp_host or not mlist.linked_newsgroup: + doc.addError(_("""You cannot enable gatewaying unless both the + news server field and + the linked + newsgroup fields are filled in.""")) + # And reset these values + mlist.gateway_to_news = 0 + mlist.gateway_to_mail = 0 diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/__init__.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/__init__.py new file mode 100644 index 0000000..5bf28bc --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Gui/__init__.py @@ -0,0 +1,32 @@ +# Copyright (C) 2001,2002 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from Archive import Archive +from Autoresponse import Autoresponse +from Bounce import Bounce +from Digest import Digest +from General import General +from Membership import Membership +from NonDigest import NonDigest +from Passwords import Passwords +from Privacy import Privacy +from Topics import Topics +from Usenet import Usenet +from Language import Language +from ContentFilter import ContentFilter + +# Don't export this symbol outside the package +del GUIBase diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/HTMLFormatter.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/HTMLFormatter.py new file mode 100644 index 0000000..7a5d69d --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/HTMLFormatter.py @@ -0,0 +1,472 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + + +"""Routines for presentation of list-specific HTML text.""" + +import time +import re + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MemberAdaptor +from Mailman.htmlformat import * + +from Mailman.i18n import _ + + +EMPTYSTRING = '' +COMMASPACE = ', ' + + + +class HTMLFormatter: + def GetMailmanFooter(self): + ownertext = COMMASPACE.join([Utils.ObscureEmail(a, 1) + for a in self.owner]) + # Remove the .Format() when htmlformat conversion is done. + realname = self.real_name + hostname = self.host_name + listinfo_link = Link(self.GetScriptURL('listinfo'), realname).Format() + owner_link = Link('mailto:' + self.GetOwnerEmail(), ownertext).Format() + innertext = _('%(listinfo_link)s list run by %(owner_link)s') + return Container( + '

        ', + #Address( + Container( + innertext, + '
        ', + Link(self.GetScriptURL('admin'), + _('%(realname)s administrative interface')), + _(' (requires authorization)'), + '
        ', + Link(Utils.ScriptURL('listinfo'), + _('Overview of all %(hostname)s mailing lists')), + '

        ', MailmanLogo())).Format() + + def FormatUsers(self, digest, lang=None): + if lang is None: + lang = self.preferred_language + conceal_sub = mm_cfg.ConcealSubscription + people = [] + if digest: + digestmembers = self.getDigestMemberKeys() + for dm in digestmembers: + if not self.getMemberOption(dm, conceal_sub): + people.append(dm) + num_concealed = len(digestmembers) - len(people) + else: + members = self.getRegularMemberKeys() + for m in members: + if not self.getMemberOption(m, conceal_sub): + people.append(m) + num_concealed = len(members) - len(people) + if num_concealed == 1: + concealed = _('(1 private member not shown)') + elif num_concealed > 1: + concealed = _( + '(%(num_concealed)d private members not shown)') + else: + concealed = '' + items = [] + people.sort() + obscure = self.obscure_addresses + for person in people: + id = Utils.ObscureEmail(person) + url = self.GetOptionsURL(person, obscure=obscure) + if obscure: + showing = Utils.ObscureEmail(person, for_text=1) + else: + showing = person + got = Link(url, showing) + if self.getDeliveryStatus(person) <> MemberAdaptor.ENABLED: + got = Italic('(', got, ')') + items.append(got) + # Just return the .Format() so this works until I finish + # converting everything to htmlformat... + return concealed + UnorderedList(*tuple(items)).Format() + + def FormatOptionButton(self, option, value, user): + if option == mm_cfg.DisableDelivery: + optval = self.getDeliveryStatus(user) <> MemberAdaptor.ENABLED + else: + optval = self.getMemberOption(user, option) + if optval == value: + checked = ' checked="checked"' + else: + checked = '' + name = {mm_cfg.DontReceiveOwnPosts : 'dontreceive', + mm_cfg.DisableDelivery : 'disablemail', + mm_cfg.DisableMime : 'mime', + mm_cfg.AcknowledgePosts : 'ackposts', + mm_cfg.Digests : 'digest', + mm_cfg.ConcealSubscription : 'conceal', + mm_cfg.SuppressPasswordReminder : 'remind', + mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic', + mm_cfg.DontReceiveDuplicates : 'nodupes', + }[option] + return '' % ( + name, value, checked) + + def FormatDigestButton(self): + if self.digest_is_default: + checked = ' checked="checked"' + else: + checked = '' + return '' % checked + + def FormatDisabledNotice(self, user): + status = self.getDeliveryStatus(user) + reason = None + info = self.getBounceInfo(user) + if status == MemberAdaptor.BYUSER: + reason = _('; it was disabled by you') + elif status == MemberAdaptor.BYADMIN: + reason = _('; it was disabled by the list administrator') + elif status == MemberAdaptor.BYBOUNCE: + date = time.strftime('%d-%b-%Y', + time.localtime(Utils.midnight(info.date))) + reason = _('''; it was disabled due to excessive bounces. The + last bounce was received on %(date)s''') + elif status == MemberAdaptor.UNKNOWN: + reason = _('; it was disabled for unknown reasons') + if reason: + note = Header(3, _('Your list delivery is currently disabled%(reason)s.')).Format() + link = Link('#mdelivery', _('Mail delivery')).Format() + mailto = Link('mailto:' + self.GetOwnerEmail(), + _('the list administrator')).Format() + note = Div(note, Paragraph(_('''You may have disabled list delivery intentionally, or it may have been triggered by bounces from your email address. In either case, to re-enable delivery, change the %(link)s option below. Contact %(mailto)s if you have any questions or need assistance.'''))).Format(css='class="message warning"') + return note + elif info and info.score > 0: + # Provide information about their current bounce score. We know + # their membership is currently enabled. + score = info.score + total = self.bounce_score_threshold + note = Div(Paragraph(_(''' + We have received some recent bounces from your + address. Your current bounce score is %(score)s out of a + maximum of %(total)s. Please double check that your subscribed + address is correct and that there are no problems with delivery to + this address. Your bounce score will be automatically reset if + the problems are corrected soon. + '''))).Format(css='class="message warning"') + return note + else: + return '' + + def FormatUmbrellaNotice(self, user, type): + addr = self.GetMemberAdminEmail(user) + if self.umbrella_list: + msg = Paragraph(_(''' + You are subscribing to a list of mailing + lists, so the %(type)s notice will be sent to + the admin address for your membership, %(addr)s. + ''')).Format() + return msg + else: + return "" + + def FormatSubscriptionMsg(self): + msg = '' + also = '' + if self.subscribe_policy == 1: + msg += _(''' + You will be sent email requesting confirmation, to + prevent others from gratuitously subscribing you. + ''') + elif self.subscribe_policy == 2: + msg += _(''' + This is a closed list, which means your subscription + will be held for approval. You will be notified of the list + moderator's decision by email. + ''') + also = _('also') + elif self.subscribe_policy == 3: + msg += _(''' + You will be sent email requesting confirmation, to + prevent others from gratuitously subscribing you. Once + confirmation is received, your request will be held for approval + by the list moderator. You will be notified of the moderator's + decision by email. + ''') + also = _("also ") + if msg: + msg += ' ' + if self.private_roster == 1: + msg += _(''' + This is %(also)s a private list, which means that the + list of members is not available to non-members. + ''') + elif self.private_roster: + msg += _(''' + This is %(also)s a hidden list, which means that the + list of members is available only to the list administrator. + ''') + else: + note = _(''' + This is %(also)s a public list, which means that the + list of members list is available to everyone. + ''') + if self.obscure_addresses: + note += _(''' + (But we obscure the addresses so they are not + easily recognizable by spammers). + ''') + msg += Paragraph(note).Format() + + if self.umbrella_list: + sfx = self.umbrella_member_suffix + msg += Paragraph(_(''' + Note that this is an umbrella list, intended to + have only other mailing lists as members. Among other things, + this means that your confirmation request will be sent to the + `%(sfx)s' account for your address. + ''')).Format() + return msg + + def FormatUndigestButton(self): + if self.digest_is_default: + checked = '' + else: + checked = ' checked="checked"' + return '' % checked + + def FormatMimeDigestsButton(self): + if self.mime_is_default_digest: + checked = ' checked="checked"' + else: + checked = '' + return '' % checked + + def FormatPlainDigestsButton(self): + if self.mime_is_default_digest: + checked = '' + else: + checked = ' checked="checked"' + return '' % checked + + def FormatEditingOption(self, lang): + realname = self.real_name + if self.private_roster == 0: + either = _('either ') + else: + either = '' + + # Text used in this section + text = _('''To unsubscribe from %(realname)s, get a password reminder, or change your subscription options %(either)senter your subscription email address:''') + if self.private_roster == 0: + text += Paragraph(_('''... or select your entry from the subscribers list (see above).''')).Format() + + # Build table where texts will be shown. + table = Table() + table.AddRow([text, + TextBox('email', size=30) + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + + + table.AddRow([Paragraph(_('''If you leave the field blank, you will be prompted for your email address.''')).Format(css='class="center"') + + Hidden('language', lang).Format() + + SubmitButton('UserOptions', + _('Unsubscribe or edit options')).Format() + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') + return table.Format() + + def RestrictedListMessage(self, which, restriction): + if not restriction: + return '' + elif restriction == 1: + return Paragraph(_('''(%(which)s is only available to the list members.)''')).Format() + else: + return Paragraph(_('''(%(which)s is only available to the list administrator.)''')).Format() + + def FormatRosterOptionForUser(self, lang): + return self.RosterOption(lang).Format() + + def RosterOption(self, lang): + container = Container() + if not self.private_roster: + container.AddItem(Paragraph(_("Click here for the list of ") + + self.real_name + + _(" subscribers: ") + + SubmitButton('SubscriberRoster', + _("Visit Subscriber list"))).Format()) + else: + if self.private_roster == 1: + only = _('members') + whom = _('Address:') + else: + only = _('the list administrator') + whom = _('Admin address:') + + # Solicit the user and password. + table = Table() + container.AddItem(self.RestrictedListMessage(_('The subscribers list'), + self.private_roster)) + container.AddItem(Paragraph(_('Enter your ') + + whom[:-1].lower() + + _(" and password to visit" + " the subscribers list:")).Format()) + table.AddRow([whom, + self.FormatBox('roster-email')]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + table.AddRow([_("Password: "), + self.FormatSecureBox('roster-pw')]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, css='class="description"') + table.AddCellInfo(table.GetCurrentRowIndex(), 1, css='class="value"') + table.AddRow([ + SubmitButton('SubscriberRoster', _("Visit Subscriber list")).Format() + + Hidden('language', lang).Format() + ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, css='class="mm_submit"') + container.AddItem(table) + return container + + def FormatFormStart(self, name, extra=''): + base_url = self.GetScriptURL(name) + if extra: + full_url = "%s/%s" % (base_url, extra) + else: + full_url = base_url + return ('
        ' % full_url) + + def FormatArchiveAnchor(self): + return '' % self.GetBaseArchiveURL() + + def FormatFormEnd(self): + return '' + + def FormatBox(self, name, size=20, value=''): + if isinstance(value, str): + safevalue = Utils.websafe(value) + else: + safevalue = value + return '' % ( + name, size, safevalue) + + def FormatSecureBox(self, name): + return '' % name + + def FormatButton(self, name, text='Submit'): + return '' % (name, text) + + def FormatReminder(self, lang): + if self.send_reminders: + return _('Once a month, your password will be emailed to you as' + ' a reminder.') + return '' + + def ParseTags(self, template, replacements, lang=None): + if lang is None: + charset = 'us-ascii' + else: + charset = Utils.GetCharSet(lang) + text = Utils.maketext(template, raw=1, lang=lang, mlist=self) + parts = re.split('(]*>)', text) + i = 1 + while i < len(parts): + tag = parts[i].lower() + if replacements.has_key(tag): + repl = replacements[tag] + if isinstance(repl, type(u'')): + repl = repl.encode(charset, 'replace') + parts[i] = repl + else: + parts[i] = '' + i = i + 2 + return EMPTYSTRING.join(parts) + + # This needs to wait until after the list is inited, so let's build it + # when it's needed only. + def GetStandardReplacements(self, lang=None): + dmember_len = len(self.getDigestMemberKeys()) + member_len = len(self.getRegularMemberKeys()) + # If only one language is enabled for this mailing list, omit the + # language choice buttons. + if len(self.GetAvailableLanguages()) == 1: + listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) + else: + listlangs = self.GetLangSelectBox(lang).Format() + # If no info is available for the mailing list, omit the info texts. + if self.info: + listabout = '

        ' + self.info.replace('\n', '
        ') + '

        ' + else: + listabout = '' + d = { + '' : self.GetMailmanFooter(), + '' : self.real_name, + '' : self._internal_name, + '' : listabout, + '' : self.FormatFormEnd(), + '' : self.FormatArchiveAnchor(), + '' : '
        ', + '' : self.FormatSubscriptionMsg(), + '' : \ + self.RestrictedListMessage(_('The current archive'), + self.archive_private), + '' : `member_len`, + '' : `dmember_len`, + '' : (`member_len + dmember_len`), + '' : '%s' % self.GetListEmail(), + '' : '%s' % self.GetRequestEmail(), + '' : self.GetOwnerEmail(), + '' : self.FormatReminder(self.preferred_language), + '' : self.host_name, + '' : listlangs, + } + + if mm_cfg.IMAGE_LOGOS: + d[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON + + # To avoid empty tags we redifine description here. + if self.description: + d[''] = Paragraph(self.description).Format() + else: + d[''] = '' + + return d + + def GetAllReplacements(self, lang=None): + """ + returns standard replaces plus formatted user lists in + a dict just like GetStandardReplacements. + """ + if lang is None: + lang = self.preferred_language + d = self.GetStandardReplacements(lang) + d.update({"": self.FormatUsers(0, lang), + "": self.FormatUsers(1, lang)}) + return d + + def GetLangSelectBox(self, lang=None, varname='language'): + if lang is None: + lang = self.preferred_language + # Figure out the available languages + values = self.GetAvailableLanguages() + legend = map(_, map(Utils.GetLanguageDescr, values)) + try: + selected = values.index(lang) + except ValueError: + try: + selected = values.index(self.preferred_language) + except ValueError: + selected = mm_cfg.DEFAULT_SERVER_LANGUAGE + # Return the widget + return SelectOptions(varname, values, legend, selected) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/MailList.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/MailList.py new file mode 100644 index 0000000..e07e23a --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/MailList.py @@ -0,0 +1,1511 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + + +"""The class representing a Mailman mailing list. + +Mixes in many task-specific classes. +""" + +import sys +import os +import time +import marshal +import errno +import re +import shutil +import socket +import urllib +import cPickle + +from cStringIO import StringIO +from UserDict import UserDict +from urlparse import urlparse +from types import * + +import email.Iterators +from email.Utils import getaddresses, formataddr, parseaddr +from email.Header import Header + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Errors +from Mailman import LockFile +from Mailman.UserDesc import UserDesc + +# base classes +from Mailman.Archiver import Archiver +from Mailman.Autoresponder import Autoresponder +from Mailman.Bouncer import Bouncer +from Mailman.Deliverer import Deliverer +from Mailman.Digester import Digester +from Mailman.GatewayManager import GatewayManager +from Mailman.HTMLFormatter import HTMLFormatter +from Mailman.ListAdmin import ListAdmin +from Mailman.SecurityManager import SecurityManager +from Mailman.TopicMgr import TopicMgr +from Mailman import Pending + +# gui components package +from Mailman import Gui + +# other useful classes +from Mailman import MemberAdaptor +from Mailman.OldStyleMemberships import OldStyleMemberships +from Mailman import Message +from Mailman import Site +from Mailman import i18n +from Mailman.Logging.Syslog import syslog + +_ = i18n._ + +EMPTYSTRING = '' + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +# Use mixins here just to avoid having any one chunk be too large. +class MailList(HTMLFormatter, Deliverer, ListAdmin, + Archiver, Digester, SecurityManager, Bouncer, GatewayManager, + Autoresponder, TopicMgr, Pending.Pending): + + # + # A MailList object's basic Python object model support + # + def __init__(self, name=None, lock=1): + # No timeout by default. If you want to timeout, open the list + # unlocked, then lock explicitly. + # + # Only one level of mixin inheritance allowed + for baseclass in self.__class__.__bases__: + if hasattr(baseclass, '__init__'): + baseclass.__init__(self) + # Initialize volatile attributes + self.InitTempVars(name) + # Default membership adaptor class + self._memberadaptor = OldStyleMemberships(self) + # This extension mechanism allows list-specific overrides of any + # method (well, except __init__(), InitTempVars(), and InitVars() + # I think). Note that fullpath() will return None when we're creating + # the list, which will only happen when name is None. + if name is None: + return + filename = os.path.join(self.fullpath(), 'extend.py') + dict = {} + try: + execfile(filename, dict) + except IOError, e: + # Ignore missing files, but log other errors + if e.errno == errno.ENOENT: + pass + else: + syslog('error', 'IOError reading list extension: %s', e) + else: + func = dict.get('extend') + if func: + func(self) + if lock: + # This will load the database. + self.Lock() + else: + self.Load() + + def __getattr__(self, name): + # Because we're using delegation, we want to be sure that attribute + # access to a delegated member function gets passed to the + # sub-objects. This of course imposes a specific name resolution + # order. + try: + return getattr(self._memberadaptor, name) + except AttributeError: + for guicomponent in self._gui: + try: + return getattr(guicomponent, name) + except AttributeError: + pass + else: + raise AttributeError, name + + def __repr__(self): + if self.Locked(): + status = '(locked)' + else: + status = '(unlocked)' + return '' % ( + self.internal_name(), status, id(self)) + + + # + # Lock management + # + def Lock(self, timeout=0): + self.__lock.lock(timeout) + # Must reload our database for consistency. Watch out for lists that + # don't exist. + try: + self.Load() + except Exception: + self.Unlock() + raise + + def Unlock(self): + self.__lock.unlock(unconditionally=1) + + def Locked(self): + return self.__lock.locked() + + + + # + # Useful accessors + # + def internal_name(self): + return self._internal_name + + def fullpath(self): + return self._full_path + + def getListAddress(self, extra=None): + if extra is None: + return '%s@%s' % (self.internal_name(), self.host_name) + return '%s-%s@%s' % (self.internal_name(), extra, self.host_name) + + # For backwards compatibility + def GetBouncesEmail(self): + return self.getListAddress('bounces') + + def GetOwnerEmail(self): + return self.getListAddress('owner') + + def GetRequestEmail(self, cookie=''): + if mm_cfg.VERP_CONFIRMATIONS and cookie: + return self.GetConfirmEmail(cookie) + else: + return self.getListAddress('request') + + def GetConfirmEmail(self, cookie): + return mm_cfg.VERP_CONFIRM_FORMAT % { + 'addr' : '%s-confirm' % self.internal_name(), + 'cookie': cookie, + } + '@' + self.host_name + + def GetConfirmJoinSubject(self, listname, cookie): + if mm_cfg.VERP_CONFIRMATIONS and cookie: + cset = i18n.get_translation().charset() or \ + Utils.GetCharSet(self.preferred_language) + subj = Header( + _('Your confirmation is required to join the %(listname)s mailing list'), + cset, header_name='subject') + return subj + else: + return 'confirm ' + cookie + + def GetConfirmLeaveSubject(self, listname, cookie): + if mm_cfg.VERP_CONFIRMATIONS and cookie: + cset = i18n.get_translation().charset() or \ + Utils.GetCharSet(self.preferred_language) + subj = Header( + _('Your confirmation is required to leave the %(listname)s mailing list'), + cset, header_name='subject') + return subj + else: + return 'confirm ' + cookie + + def GetListEmail(self): + return self.getListAddress() + + def GetMemberAdminEmail(self, member): + """Usually the member addr, but modified for umbrella lists. + + Umbrella lists have other mailing lists as members, and so admin stuff + like confirmation requests and passwords must not be sent to the + member addresses - the sublists - but rather to the administrators of + the sublists. This routine picks the right address, considering + regular member address to be their own administrative addresses. + + """ + if not self.umbrella_list: + return member + else: + acct, host = tuple(member.split('@')) + return "%s%s@%s" % (acct, self.umbrella_member_suffix, host) + + def GetScriptURL(self, scriptname, absolute=0): + return Utils.ScriptURL(scriptname, self.web_page_url, absolute) + \ + '/' + self.internal_name() + + def GetOptionsURL(self, user, obscure=0, absolute=0): + url = self.GetScriptURL('options', absolute) + if obscure: + user = Utils.ObscureEmail(user) + return '%s/%s' % (url, urllib.quote(user.lower())) + + + # + # Instance and subcomponent initialization + # + def InitTempVars(self, name): + """Set transient variables of this and inherited classes.""" + # The timestamp is set whenever we load the state from disk. If our + # timestamp is newer than the modtime of the config.pck file, we don't + # need to reload, otherwise... we do. + self.__timestamp = 0 + self.__lock = LockFile.LockFile( + os.path.join(mm_cfg.LOCK_DIR, name or '') + '.lock', + # TBD: is this a good choice of lifetime? + lifetime = mm_cfg.LIST_LOCK_LIFETIME, + withlogging = mm_cfg.LIST_LOCK_DEBUGGING) + self._internal_name = name + if name: + self._full_path = Site.get_listpath(name) + else: + self._full_path = '' + # Only one level of mixin inheritance allowed + for baseclass in self.__class__.__bases__: + if hasattr(baseclass, 'InitTempVars'): + baseclass.InitTempVars(self) + # Now, initialize our gui components + self._gui = [] + for component in dir(Gui): + if component.startswith('_'): + continue + self._gui.append(getattr(Gui, component)()) + + def InitVars(self, name=None, admin='', crypted_password=''): + """Assign default values - some will be overriden by stored state.""" + # Non-configurable list info + if name: + self._internal_name = name + + # When was the list created? + self.created_at = time.time() + + # Must save this state, even though it isn't configurable + self.volume = 1 + self.members = {} # self.digest_members is initted in mm_digest + self.data_version = mm_cfg.DATA_FILE_VERSION + self.last_post_time = 0 + + self.post_id = 1. # A float so it never has a chance to overflow. + self.user_options = {} + self.language = {} + self.usernames = {} + self.passwords = {} + self.new_member_options = mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS + + # This stuff is configurable + self.respond_to_post_requests = 1 + self.advertised = mm_cfg.DEFAULT_LIST_ADVERTISED + self.max_num_recipients = mm_cfg.DEFAULT_MAX_NUM_RECIPIENTS + self.max_message_size = mm_cfg.DEFAULT_MAX_MESSAGE_SIZE + # See the note in Defaults.py concerning DEFAULT_HOST_NAME + # vs. DEFAULT_EMAIL_HOST. + self.host_name = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST + self.web_page_url = ( + mm_cfg.DEFAULT_URL or + mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST) + self.owner = [admin] + self.moderator = [] + self.reply_goes_to_list = mm_cfg.DEFAULT_REPLY_GOES_TO_LIST + self.reply_to_address = '' + self.first_strip_reply_to = mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO + self.admin_immed_notify = mm_cfg.DEFAULT_ADMIN_IMMED_NOTIFY + self.admin_notify_mchanges = \ + mm_cfg.DEFAULT_ADMIN_NOTIFY_MCHANGES + self.require_explicit_destination = \ + mm_cfg.DEFAULT_REQUIRE_EXPLICIT_DESTINATION + self.acceptable_aliases = mm_cfg.DEFAULT_ACCEPTABLE_ALIASES + self.umbrella_list = mm_cfg.DEFAULT_UMBRELLA_LIST + self.umbrella_member_suffix = \ + mm_cfg.DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX + self.send_reminders = mm_cfg.DEFAULT_SEND_REMINDERS + self.send_welcome_msg = mm_cfg.DEFAULT_SEND_WELCOME_MSG + self.send_goodbye_msg = mm_cfg.DEFAULT_SEND_GOODBYE_MSG + self.bounce_matching_headers = \ + mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS + self.header_filter_rules = [] + self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST + internalname = self.internal_name() + self.real_name = internalname[0].upper() + internalname[1:] + self.description = '' + self.info = '' + self.welcome_msg = '' + self.goodbye_msg = '' + self.subscribe_policy = mm_cfg.DEFAULT_SUBSCRIBE_POLICY + self.unsubscribe_policy = mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY + self.private_roster = mm_cfg.DEFAULT_PRIVATE_ROSTER + self.obscure_addresses = mm_cfg.DEFAULT_OBSCURE_ADDRESSES + self.admin_member_chunksize = mm_cfg.DEFAULT_ADMIN_MEMBER_CHUNKSIZE + self.administrivia = mm_cfg.DEFAULT_ADMINISTRIVIA + self.preferred_language = mm_cfg.DEFAULT_SERVER_LANGUAGE + self.available_languages = [] + self.include_rfc2369_headers = 1 + self.include_list_post_header = 1 + self.filter_mime_types = mm_cfg.DEFAULT_FILTER_MIME_TYPES + self.pass_mime_types = mm_cfg.DEFAULT_PASS_MIME_TYPES + self.filter_filename_extensions = \ + mm_cfg.DEFAULT_FILTER_FILENAME_EXTENSIONS + self.pass_filename_extensions = mm_cfg.DEFAULT_PASS_FILENAME_EXTENSIONS + self.filter_content = mm_cfg.DEFAULT_FILTER_CONTENT + self.collapse_alternatives = mm_cfg.DEFAULT_COLLAPSE_ALTERNATIVES + self.convert_html_to_plaintext = \ + mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT + self.filter_action = mm_cfg.DEFAULT_FILTER_ACTION + # Analogs to these are initted in Digester.InitVars + self.nondigestable = mm_cfg.DEFAULT_NONDIGESTABLE + self.personalize = 0 + # New sender-centric moderation (privacy) options + self.default_member_moderation = \ + mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION + # Emergency moderation bit + self.emergency = 0 + # This really ought to default to mm_cfg.HOLD, but that doesn't work + # with the current GUI description model. So, 0==Hold, 1==Reject, + # 2==Discard + self.member_moderation_action = 0 + self.member_moderation_notice = '' + self.accept_these_nonmembers = [] + self.hold_these_nonmembers = [] + self.reject_these_nonmembers = [] + self.discard_these_nonmembers = [] + self.forward_auto_discards = mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS + self.generic_nonmember_action = mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION + self.nonmember_rejection_notice = '' + # Ban lists + self.ban_list = [] + # BAW: This should really be set in SecurityManager.InitVars() + self.password = crypted_password + # Max autoresponses per day. A mapping between addresses and a + # 2-tuple of the date of the last autoresponse and the number of + # autoresponses sent on that date. + self.hold_and_cmd_autoresponses = {} + # Only one level of mixin inheritance allowed + for baseclass in self.__class__.__bases__: + if hasattr(baseclass, 'InitVars'): + baseclass.InitVars(self) + + # These need to come near the bottom because they're dependent on + # other settings. + self.subject_prefix = mm_cfg.DEFAULT_SUBJECT_PREFIX % self.__dict__ + self.msg_header = mm_cfg.DEFAULT_MSG_HEADER + self.msg_footer = mm_cfg.DEFAULT_MSG_FOOTER + # Set this to Never if the list's preferred language uses us-ascii, + # otherwise set it to As Needed + if Utils.GetCharSet(self.preferred_language) == 'us-ascii': + self.encode_ascii_prefixes = 0 + else: + self.encode_ascii_prefixes = 2 + # scrub regular delivery + self.scrub_nondigest = mm_cfg.DEFAULT_SCRUB_NONDIGEST + # automatic discarding + self.max_days_to_hold = mm_cfg.DEFAULT_MAX_DAYS_TO_HOLD + + + # + # Web API support via administrative categories + # + def GetConfigCategories(self): + class CategoryDict(UserDict): + def __init__(self): + UserDict.__init__(self) + self.keysinorder = mm_cfg.ADMIN_CATEGORIES[:] + def keys(self): + return self.keysinorder + def items(self): + items = [] + for k in mm_cfg.ADMIN_CATEGORIES: + items.append((k, self.data[k])) + return items + def values(self): + values = [] + for k in mm_cfg.ADMIN_CATEGORIES: + values.append(self.data[k]) + return values + + categories = CategoryDict() + # Only one level of mixin inheritance allowed + for gui in self._gui: + k, v = gui.GetConfigCategory() + categories[k] = (v, gui) + return categories + + def GetConfigSubCategories(self, category): + for gui in self._gui: + if hasattr(gui, 'GetConfigSubCategories'): + # Return the first one that knows about the given subcategory + subcat = gui.GetConfigSubCategories(category) + if subcat is not None: + return subcat + return None + + def GetConfigInfo(self, category, subcat=None): + for gui in self._gui: + if hasattr(gui, 'GetConfigInfo'): + value = gui.GetConfigInfo(self, category, subcat) + if value: + return value + + + # + # List creation + # + def Create(self, name, admin, crypted_password, + langs=None, emailhost=None): + if Utils.list_exists(name): + raise Errors.MMListAlreadyExistsError, name + # Validate what will be the list's posting address. If that's + # invalid, we don't want to create the mailing list. The hostname + # part doesn't really matter, since that better already be valid. + # However, most scripts already catch MMBadEmailError as exceptions on + # the admin's email address, so transform the exception. + if emailhost is None: + emailhost = mm_cfg.DEFAULT_EMAIL_HOST + postingaddr = '%s@%s' % (name, emailhost) + try: + Utils.ValidateEmail(postingaddr) + except Errors.MMBadEmailError: + raise Errors.BadListNameError, postingaddr + # Validate the admin's email address + Utils.ValidateEmail(admin) + self._internal_name = name + self._full_path = Site.get_listpath(name, create=1) + # Don't use Lock() since that tries to load the non-existant config.pck + self.__lock.lock() + self.InitVars(name, admin, crypted_password) + self.CheckValues() + if langs is None: + self.available_languages = [self.preferred_language] + else: + self.available_languages = langs + + + + # + # Database and filesystem I/O + # + def __save(self, dict): + # Save the file as a binary pickle, and rotate the old version to a + # backup file. We must guarantee that config.pck is always valid so + # we never rotate unless the we've successfully written the temp file. + # We use pickle now because marshal is not guaranteed to be compatible + # between Python versions. + fname = os.path.join(self.fullpath(), 'config.pck') + fname_tmp = fname + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) + fname_last = fname + '.last' + fp = None + try: + fp = open(fname_tmp, 'w') + # Use a binary format... it's more efficient. + cPickle.dump(dict, fp, 1) + fp.flush() + if mm_cfg.SYNC_AFTER_WRITE: + os.fsync(fp.fileno()) + fp.close() + except IOError, e: + syslog('error', + 'Failed config.pck write, retaining old state.\n%s', e) + if fp is not None: + os.unlink(fname_tmp) + raise + # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation + # as safely as possible. + try: + # might not exist yet + os.unlink(fname_last) + except OSError, e: + if e.errno <> errno.ENOENT: raise + try: + # might not exist yet + os.link(fname, fname_last) + except OSError, e: + if e.errno <> errno.ENOENT: raise + os.rename(fname_tmp, fname) + # Reset the timestamp + self.__timestamp = os.path.getmtime(fname) + + def Save(self): + # Refresh the lock, just to let other processes know we're still + # interested in it. This will raise a NotLockedError if we don't have + # the lock (which is a serious problem!). TBD: do we need to be more + # defensive? + self.__lock.refresh() + # copy all public attributes to serializable dictionary + dict = {} + for key, value in self.__dict__.items(): + if key[0] == '_' or type(value) is MethodType: + continue + dict[key] = value + # Make config.pck unreadable by `other', as it contains all the + # list members' passwords (in clear text). + omask = os.umask(007) + try: + self.__save(dict) + finally: + os.umask(omask) + self.SaveRequestsDb() + self.CheckHTMLArchiveDir() + + def __load(self, dbfile): + # Attempt to load and unserialize the specified database file. This + # could actually be a config.db (for pre-2.1alpha3) or config.pck, + # i.e. a marshal or a binary pickle. Actually, it could also be a + # .last backup file if the primary storage file was corrupt. The + # decision on whether to unpickle or unmarshal is based on the file + # extension, but we always save it using pickle (since only it, and + # not marshal is guaranteed to be compatible across Python versions). + # + # On success return a 2-tuple of (dictionary, None). On error, return + # a 2-tuple of the form (None, errorobj). + if dbfile.endswith('.db') or dbfile.endswith('.db.last'): + loadfunc = marshal.load + elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): + loadfunc = cPickle.load + else: + assert 0, 'Bad database file name' + try: + # Check the mod time of the file first. If it matches our + # timestamp, then the state hasn't change since the last time we + # loaded it. Otherwise open the file for loading, below. If the + # file doesn't exist, we'll get an EnvironmentError with errno set + # to ENOENT (EnvironmentError is the base class of IOError and + # OSError). + mtime = os.path.getmtime(dbfile) + if mtime <= self.__timestamp: + # File is not newer + return None, None + fp = open(dbfile) + except EnvironmentError, e: + if e.errno <> errno.ENOENT: raise + # The file doesn't exist yet + return None, e + try: + try: + dict = loadfunc(fp) + if type(dict) <> DictType: + return None, 'Load() expected to return a dictionary' + except (EOFError, ValueError, TypeError, MemoryError, + cPickle.PicklingError, cPickle.UnpicklingError), e: + return None, e + finally: + fp.close() + # Update timestamp + self.__timestamp = mtime + return dict, None + + def Load(self, check_version=True): + if not Utils.list_exists(self.internal_name()): + raise Errors.MMUnknownListError + # We first try to load config.pck, which contains the up-to-date + # version of the database. If that fails, perhaps because it's + # corrupted or missing, we'll try to load the backup file + # config.pck.last. + # + # Should both of those fail, we'll look for config.db and + # config.db.last for backwards compatibility with pre-2.1alpha3 + pfile = os.path.join(self.fullpath(), 'config.pck') + plast = pfile + '.last' + dfile = os.path.join(self.fullpath(), 'config.db') + dlast = dfile + '.last' + for file in (pfile, plast, dfile, dlast): + dict, e = self.__load(file) + if dict is None: + if e is not None: + # Had problems with this file; log it and try the next one. + syslog('error', "couldn't load config file %s\n%s", + file, e) + else: + # We already have the most up-to-date state + return + else: + break + else: + # Nothing worked, so we have to give up + syslog('error', 'All %s fallbacks were corrupt, giving up', + self.internal_name()) + raise Errors.MMCorruptListDatabaseError, e + # Now, if we didn't end up using the primary database file, we want to + # copy the fallback into the primary so that the logic in Save() will + # still work. For giggles, we'll copy it to a safety backup. Note we + # MUST do this with the underlying list lock acquired. + if file == plast or file == dlast: + syslog('error', 'fixing corrupt config file, using: %s', file) + unlock = True + try: + try: + self.__lock.lock() + except LockFile.AlreadyLockedError: + unlock = False + self.__fix_corrupt_pckfile(file, pfile, plast, dfile, dlast) + finally: + if unlock: + self.__lock.unlock() + # Copy the loaded dictionary into the attributes of the current + # mailing list object, then run sanity check on the data. + self.__dict__.update(dict) + if check_version: + self.CheckVersion(dict) + self.CheckValues() + + def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast): + if file == plast: + # Move aside any existing pickle file and delete any existing + # safety file. This avoids EPERM errors inside the shutil.copy() + # calls if those files exist with different ownership. + try: + os.rename(pfile, pfile + '.corrupt') + except OSError, e: + if e.errno <> errno.ENOENT: raise + try: + os.remove(pfile + '.safety') + except OSError, e: + if e.errno <> errno.ENOENT: raise + shutil.copy(file, pfile) + shutil.copy(file, pfile + '.safety') + elif file == dlast: + # Move aside any existing marshal file and delete any existing + # safety file. This avoids EPERM errors inside the shutil.copy() + # calls if those files exist with different ownership. + try: + os.rename(dfile, dfile + '.corrupt') + except OSError, e: + if e.errno <> errno.ENOENT: raise + try: + os.remove(dfile + '.safety') + except OSError, e: + if e.errno <> errno.ENOENT: raise + shutil.copy(file, dfile) + shutil.copy(file, dfile + '.safety') + + + # + # Sanity checks + # + def CheckVersion(self, stored_state): + """Auto-update schema if necessary.""" + if self.data_version >= mm_cfg.DATA_FILE_VERSION: + return + # Initialize any new variables + self.InitVars() + # Then reload the database (but don't recurse). Force a reload even + # if we have the most up-to-date state. + self.__timestamp = 0 + self.Load(check_version=0) + # We must hold the list lock in order to update the schema + waslocked = self.Locked() + if not waslocked: + self.Lock() + try: + from versions import Update + Update(self, stored_state) + self.data_version = mm_cfg.DATA_FILE_VERSION + self.Save() + finally: + if not waslocked: + self.Unlock() + + def CheckValues(self): + """Normalize selected values to known formats.""" + if '' in urlparse(self.web_page_url)[:2]: + # Either the "scheme" or the "network location" part of the parsed + # URL is empty; substitute faulty value with (hopefully sane) + # default. Note that DEFAULT_URL is obsolete. + self.web_page_url = ( + mm_cfg.DEFAULT_URL or + mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST) + if self.web_page_url and self.web_page_url[-1] <> '/': + self.web_page_url = self.web_page_url + '/' + # Legacy reply_to_address could be an illegal value. We now verify + # upon setting and don't check it at the point of use. + try: + if self.reply_to_address.strip() and self.reply_goes_to_list: + Utils.ValidateEmail(self.reply_to_address) + except Errors.EmailAddressError: + syslog('error', 'Bad reply_to_address "%s" cleared for list: %s', + self.reply_to_address, self.internal_name()) + self.reply_to_address = '' + self.reply_goes_to_list = 0 + # Legacy topics may have bad regular expressions in their patterns + goodtopics = [] + for name, pattern, desc, emptyflag in self.topics: + try: + re.compile(pattern) + except (re.error, TypeError): + syslog('error', 'Bad topic pattern "%s" for list: %s', + pattern, self.internal_name()) + else: + goodtopics.append((name, pattern, desc, emptyflag)) + self.topics = goodtopics + + + # + # Membership management front-ends and assertion checks + # + def InviteNewMember(self, userdesc, text=''): + """Invite a new member to the list. + + This is done by creating a subscription pending for the user, and then + crafting a message to the member informing them of the invitation. + """ + invitee = userdesc.address + Utils.ValidateEmail(invitee) + # check for banned address + pattern = self.GetBannedPattern(invitee) + if pattern: + raise Errors.MembershipIsBanned, pattern + # Hack alert! Squirrel away a flag that only invitations have, so + # that we can do something slightly different when an invitation + # subscription is confirmed. In those cases, we don't need further + # admin approval, even if the list is so configured. The flag is the + # list name to prevent invitees from cross-subscribing. + userdesc.invitation = self.internal_name() + cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) + requestaddr = self.getListAddress('request') + confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), + cookie) + listname = self.real_name + text += Utils.maketext( + 'invite.txt', + {'email' : invitee, + 'listname' : listname, + 'hostname' : self.host_name, + 'confirmurl' : confirmurl, + 'requestaddr': requestaddr, + 'cookie' : cookie, + 'listowner' : self.GetOwnerEmail(), + }, mlist=self) + sender = self.GetRequestEmail(cookie) + msg = Message.UserNotification( + invitee, sender, + text=text, lang=self.preferred_language) + subj = self.GetConfirmJoinSubject(listname, cookie) + del msg['subject'] + msg['Subject'] = subj + msg.send(self) + + def AddMember(self, userdesc, remote=None): + """Front end to member subscription. + + This method enforces subscription policy, validates values, sends + notifications, and any other grunt work involved in subscribing a + user. It eventually calls ApprovedAddMember() to do the actual work + of subscribing the user. + + userdesc is an instance with the following public attributes: + + address -- the unvalidated email address of the member + fullname -- the member's full name (i.e. John Smith) + digest -- a flag indicating whether the user wants digests or not + language -- the requested default language for the user + password -- the user's password + + Other attributes may be defined later. Only address is required; the + others all have defaults (fullname='', digests=0, language=list's + preferred language, password=generated). + + remote is a string which describes where this add request came from. + """ + assert self.Locked() + # Suck values out of userdesc, apply defaults, and reset the userdesc + # attributes (for passing on to ApprovedAddMember()). Lowercase the + # addr's domain part. + email = Utils.LCDomain(userdesc.address) + name = getattr(userdesc, 'fullname', '') + lang = getattr(userdesc, 'language', self.preferred_language) + digest = getattr(userdesc, 'digest', None) + password = getattr(userdesc, 'password', Utils.MakeRandomPassword()) + if digest is None: + if self.nondigestable: + digest = 0 + else: + digest = 1 + # Validate the e-mail address to some degree. + Utils.ValidateEmail(email) + if self.isMember(email): + raise Errors.MMAlreadyAMember, email + if email.lower() == self.GetListEmail().lower(): + # Trying to subscribe the list to itself! + raise Errors.MMBadEmailError + realname = self.real_name + # Is the subscribing address banned from this list? + pattern = self.GetBannedPattern(email) + if pattern: + syslog('vette', '%s banned subscription: %s (matched: %s)', + realname, email, pattern) + raise Errors.MembershipIsBanned, pattern + # Sanity check the digest flag + if digest and not self.digestable: + raise Errors.MMCantDigestError + elif not digest and not self.nondigestable: + raise Errors.MMMustDigestError + + userdesc.address = email + userdesc.fullname = name + userdesc.digest = digest + userdesc.language = lang + userdesc.password = password + + # Apply the list's subscription policy. 0 means open subscriptions; 1 + # means the user must confirm; 2 means the admin must approve; 3 means + # the user must confirm and then the admin must approve + if self.subscribe_policy == 0: + self.ApprovedAddMember(userdesc, whence=remote or '') + elif self.subscribe_policy == 1 or self.subscribe_policy == 3: + # User confirmation required. BAW: this should probably just + # accept a userdesc instance. + cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) + # Send the user the confirmation mailback + if remote is None: + by = remote = '' + else: + by = ' ' + remote + remote = _(' from %(remote)s') + + recipient = self.GetMemberAdminEmail(email) + confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), + cookie) + text = Utils.maketext( + 'verify.txt', + {'email' : email, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, + 'requestaddr' : self.getListAddress('request'), + 'remote' : remote, + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, + }, lang=lang, mlist=self) + msg = Message.UserNotification( + recipient, self.GetRequestEmail(cookie), + text=text, lang=lang) + # BAW: See ChangeMemberAddress() for why we do it this way... + del msg['subject'] + msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie) + msg['Reply-To'] = self.GetRequestEmail(cookie) + msg.send(self) + who = formataddr((name, email)) + syslog('subscribe', '%s: pending %s %s', + self.internal_name(), who, by) + raise Errors.MMSubscribeNeedsConfirmation + else: + # Subscription approval is required. Add this entry to the admin + # requests database. BAW: this should probably take a userdesc + # just like above. + self.HoldSubscription(email, name, password, digest, lang) + raise Errors.MMNeedApproval, _( + 'subscriptions to %(realname)s require moderator approval') + + def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', + whence=''): + """Add a member right now. + + The member's subscription must be approved by what ever policy the + list enforces. + + userdesc is as above in AddMember(). + + ack is a flag that specifies whether the user should get an + acknowledgement of their being subscribed. Default is to use the + list's default flag value. + + admin_notif is a flag that specifies whether the list owner should get + an acknowledgement of this subscription. Default is to use the list's + default flag value. + """ + assert self.Locked() + # Set up default flag values + if ack is None: + ack = self.send_welcome_msg + if admin_notif is None: + admin_notif = self.admin_notify_mchanges + # Suck values out of userdesc, and apply defaults. + email = Utils.LCDomain(userdesc.address) + name = getattr(userdesc, 'fullname', '') + lang = getattr(userdesc, 'language', self.preferred_language) + digest = getattr(userdesc, 'digest', None) + password = getattr(userdesc, 'password', Utils.MakeRandomPassword()) + if digest is None: + if self.nondigestable: + digest = 0 + else: + digest = 1 + # Let's be extra cautious + Utils.ValidateEmail(email) + if self.isMember(email): + raise Errors.MMAlreadyAMember, email + # Check for banned address here too for admin mass subscribes + # and confirmations. + pattern = self.GetBannedPattern(email) + if pattern: + raise Errors.MembershipIsBanned, pattern + # Do the actual addition + self.addNewMember(email, realname=name, digest=digest, + password=password, language=lang) + self.setMemberOption(email, mm_cfg.DisableMime, + 1 - self.mime_is_default_digest) + self.setMemberOption(email, mm_cfg.Moderate, + self.default_member_moderation) + # Now send and log results + if digest: + kind = ' (digest)' + else: + kind = '' + syslog('subscribe', '%s: new%s %s, %s', self.internal_name(), + kind, formataddr((name, email)), whence) + if ack: + self.SendSubscribeAck(email, self.getMemberPassword(email), + digest, text) + if admin_notif: + lang = self.preferred_language + otrans = i18n.get_translation() + i18n.set_language(lang) + try: + realname = self.real_name + subject = _('%(realname)s subscription notification') + finally: + i18n.set_translation(otrans) + if isinstance(name, UnicodeType): + name = name.encode(Utils.GetCharSet(lang), 'replace') + text = Utils.maketext( + "adminsubscribeack.txt", + {"listname" : realname, + "member" : formataddr((name, email)), + }, mlist=self) + msg = Message.OwnerNotification(self, subject, text) + msg.send(self) + + def DeleteMember(self, name, whence=None, admin_notif=None, userack=True): + realname, email = parseaddr(name) + if self.unsubscribe_policy == 0: + self.ApprovedDeleteMember(name, whence, admin_notif, userack) + else: + self.HoldUnsubscription(email) + raise Errors.MMNeedApproval, _( + 'unsubscriptions require moderator approval') + + def ApprovedDeleteMember(self, name, whence=None, + admin_notif=None, userack=None): + if userack is None: + userack = self.send_goodbye_msg + if admin_notif is None: + admin_notif = self.admin_notify_mchanges + # Delete a member, for which we know the approval has been made + fullname, emailaddr = parseaddr(name) + userlang = self.getMemberLanguage(emailaddr) + # Remove the member + self.removeMember(emailaddr) + # And send an acknowledgement to the user... + if userack: + self.SendUnsubscribeAck(emailaddr, userlang) + # ...and to the administrator + if admin_notif: + realname = self.real_name + subject = _('%(realname)s unsubscribe notification') + text = Utils.maketext( + 'adminunsubscribeack.txt', + {'member' : name, + 'listname': self.real_name, + }, mlist=self) + msg = Message.OwnerNotification(self, subject, text) + msg.send(self) + if whence: + whence = "; %s" % whence + else: + whence = "" + syslog('subscribe', '%s: deleted %s%s', + self.internal_name(), name, whence) + + def ChangeMemberName(self, addr, name, globally): + self.setMemberName(addr, name) + if not globally: + return + for listname in Utils.list_names(): + # Don't bother with ourselves + if listname == self.internal_name(): + continue + mlist = MailList(listname, lock=0) + if mlist.host_name <> self.host_name: + continue + if not mlist.isMember(addr): + continue + mlist.Lock() + try: + mlist.setMemberName(addr, name) + mlist.Save() + finally: + mlist.Unlock() + + def ChangeMemberAddress(self, oldaddr, newaddr, globally): + # Changing a member address consists of verifying the new address, + # making sure the new address isn't already a member, and optionally + # going through the confirmation process. + # + # Most of these checks are copied from AddMember + newaddr = Utils.LCDomain(newaddr) + Utils.ValidateEmail(newaddr) + # Raise an exception if this email address is already a member of the + # list, but only if the new address is the same case-wise as the old + # address and we're not doing a global change. + if not globally and newaddr == oldaddr and self.isMember(newaddr): + raise Errors.MMAlreadyAMember + if newaddr == self.GetListEmail().lower(): + raise Errors.MMBadEmailError + realname = self.real_name + # Don't allow changing to a banned address. MAS: maybe we should + # unsubscribe the oldaddr too just for trying, but that's probably + # too harsh. + pattern = self.GetBannedPattern(newaddr) + if pattern: + syslog('vette', + '%s banned address change: %s -> %s (matched: %s)', + realname, oldaddr, newaddr, pattern) + raise Errors.MembershipIsBanned, pattern + # Pend the subscription change + cookie = self.pend_new(Pending.CHANGE_OF_ADDRESS, + oldaddr, newaddr, globally) + confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), + cookie) + lang = self.getMemberLanguage(oldaddr) + text = Utils.maketext( + 'verify.txt', + {'email' : newaddr, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, + 'requestaddr': self.getListAddress('request'), + 'remote' : '', + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, + }, lang=lang, mlist=self) + # BAW: We don't pass the Subject: into the UserNotification + # constructor because it will encode it in the charset of the language + # being used. For non-us-ascii charsets, this means it will probably + # quopri quote it, and thus replies will also be quopri encoded. But + # CommandRunner doesn't yet grok such headers. So, just set the + # Subject: in a separate step, although we have to delete the one + # UserNotification adds. + msg = Message.UserNotification( + newaddr, self.GetRequestEmail(cookie), + text=text, lang=lang) + del msg['subject'] + msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie) + msg['Reply-To'] = self.GetRequestEmail(cookie) + msg.send(self) + + def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally): + # Check here for banned address in case address was banned after + # confirmation was mailed. MAS: If it's global change should we just + # skip this list and proceed to the others? For now we'll throw the + # exception. + pattern = self.GetBannedPattern(newaddr) + if pattern: + raise Errors.MembershipIsBanned, pattern + # It's possible they were a member of this list, but choose to change + # their membership globally. In that case, we simply remove the old + # address. + if self.getMemberCPAddress(oldaddr) == newaddr: + self.removeMember(oldaddr) + else: + self.changeMemberAddress(oldaddr, newaddr) + # If globally is true, then we also include every list for which + # oldaddr is a member. + if not globally: + return + for listname in Utils.list_names(): + # Don't bother with ourselves + if listname == self.internal_name(): + continue + mlist = MailList(listname, lock=0) + if mlist.host_name <> self.host_name: + continue + if not mlist.isMember(oldaddr): + continue + # If new address is banned from this list, just skip it. + if mlist.GetBannedPattern(newaddr): + continue + mlist.Lock() + try: + # Same logic as above, re newaddr is already a member + if mlist.getMemberCPAddress(oldaddr) == newaddr: + mlist.removeMember(oldaddr) + else: + mlist.changeMemberAddress(oldaddr, newaddr) + mlist.Save() + finally: + mlist.Unlock() + + + # + # Confirmation processing + # + def ProcessConfirmation(self, cookie, context=None): + rec = self.pend_confirm(cookie) + if rec is None: + raise Errors.MMBadConfirmation, 'No cookie record for %s' % cookie + try: + op = rec[0] + data = rec[1:] + except ValueError: + raise Errors.MMBadConfirmation, 'op-less data %s' % (rec,) + if op == Pending.SUBSCRIPTION: + whence = 'via email confirmation' + try: + userdesc = data[0] + # If confirmation comes from the web, context should be a + # UserDesc instance which contains overrides of the original + # subscription information. If it comes from email, then + # context is a Message and isn't relevant, so ignore it. + if isinstance(context, UserDesc): + userdesc += context + whence = 'via web confirmation' + addr = userdesc.address + fullname = userdesc.fullname + password = userdesc.password + digest = userdesc.digest + lang = userdesc.language + except ValueError: + raise Errors.MMBadConfirmation, 'bad subscr data %s' % (data,) + # Hack alert! Was this a confirmation of an invitation? + invitation = getattr(userdesc, 'invitation', False) + # We check for both 2 (approval required) and 3 (confirm + + # approval) because the policy could have been changed in the + # middle of the confirmation dance. + if invitation: + if invitation <> self.internal_name(): + # Not cool. The invitee was trying to subscribe to a + # different list than they were invited to. Alert both + # list administrators. + self.SendHostileSubscriptionNotice(invitation, addr) + raise Errors.HostileSubscriptionError + elif self.subscribe_policy in (2, 3): + self.HoldSubscription(addr, fullname, password, digest, lang) + name = self.real_name + raise Errors.MMNeedApproval, _( + 'subscriptions to %(name)s require administrator approval') + self.ApprovedAddMember(userdesc, whence=whence) + return op, addr, password, digest, lang + elif op == Pending.UNSUBSCRIPTION: + addr = data[0] + # Log file messages don't need to be i18n'd + if isinstance(context, Message.Message): + whence = 'email confirmation' + else: + whence = 'web confirmation' + # Can raise NotAMemberError if they unsub'd via other means + self.ApprovedDeleteMember(addr, whence=whence) + return op, addr + elif op == Pending.CHANGE_OF_ADDRESS: + oldaddr, newaddr, globally = data + self.ApprovedChangeMemberAddress(oldaddr, newaddr, globally) + return op, oldaddr, newaddr + elif op == Pending.HELD_MESSAGE: + id = data[0] + approved = None + # Confirmation should be coming from email, where context should + # be the confirming message. If the message does not have an + # Approved: header, this is a discard. If it has an Approved: + # header that does not match the list password, then we'll notify + # the list administrator that they used the wrong password. + # Otherwise it's an approval. + if isinstance(context, Message.Message): + # See if it's got an Approved: header, either in the headers, + # or in the first text/plain section of the response. For + # robustness, we'll accept Approve: as well. + approved = context.get('Approved', context.get('Approve')) + if not approved: + try: + subpart = list(email.Iterators.typed_subpart_iterator( + context, 'text', 'plain'))[0] + except IndexError: + subpart = None + if subpart: + s = StringIO(subpart.get_payload()) + while True: + line = s.readline() + if not line: + break + if not line.strip(): + continue + i = line.find(':') + if i > 0: + if (line[:i].lower() == 'approve' or + line[:i].lower() == 'approved'): + # then + approved = line[i+1:].strip() + break + # Is there an approved header? + if approved is not None: + # Does it match the list password? Note that we purposefully + # do not allow the site password here. + if self.Authenticate([mm_cfg.AuthListAdmin, + mm_cfg.AuthListModerator], + approved) <> mm_cfg.UnAuthorized: + action = mm_cfg.APPROVE + else: + # The password didn't match. Re-pend the message and + # inform the list moderators about the problem. + self.pend_repend(cookie, rec) + raise Errors.MMBadPasswordError + else: + action = mm_cfg.DISCARD + try: + self.HandleRequest(id, action) + except KeyError: + # Most likely because the message has already been disposed of + # via the admindb page. + syslog('error', 'Could not process HELD_MESSAGE: %s', id) + return (op,) + elif op == Pending.RE_ENABLE: + member = data[1] + self.setDeliveryStatus(member, MemberAdaptor.ENABLED) + return op, member + else: + assert 0, 'Bad op: %s' % op + + def ConfirmUnsubscription(self, addr, lang=None, remote=None): + if lang is None: + lang = self.getMemberLanguage(addr) + cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) + confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), + cookie) + realname = self.real_name + if remote is not None: + by = " " + remote + remote = _(" from %(remote)s") + else: + by = "" + remote = "" + text = Utils.maketext( + 'unsub.txt', + {'email' : addr, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, + 'requestaddr' : self.getListAddress('request'), + 'remote' : remote, + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, + }, lang=lang, mlist=self) + msg = Message.UserNotification( + addr, self.GetRequestEmail(cookie), + text=text, lang=lang) + # BAW: See ChangeMemberAddress() for why we do it this way... + del msg['subject'] + msg['Subject'] = self.GetConfirmLeaveSubject(realname, cookie) + msg['Reply-To'] = self.GetRequestEmail(cookie) + msg.send(self) + + + # + # Miscellaneous stuff + # + def HasExplicitDest(self, msg): + """True if list name or any acceptable_alias is included among the + addresses in the recipient headers. + """ + # This is the list's full address. + listfullname = '%s@%s' % (self.internal_name(), self.host_name) + recips = [] + # Check all recipient addresses against the list's explicit addresses, + # specifically To: Cc: and Resent-to: + to = [] + for header in ('to', 'cc', 'resent-to', 'resent-cc'): + to.extend(getaddresses(msg.get_all(header, []))) + for fullname, addr in to: + # It's possible that if the header doesn't have a valid RFC 2822 + # value, we'll get None for the address. So skip it. + if addr is None: + continue + addr = addr.lower() + localpart = addr.split('@')[0] + if (# TBD: backwards compatibility: deprecated + localpart == self.internal_name() or + # exact match against the complete list address + addr == listfullname): + return True + recips.append((addr, localpart)) + # Helper function used to match a pattern against an address. + def domatch(pattern, addr): + try: + if re.match(pattern, addr, re.IGNORECASE): + return True + except re.error: + # The pattern is a malformed regexp -- try matching safely, + # with all non-alphanumerics backslashed: + if re.match(re.escape(pattern), addr, re.IGNORECASE): + return True + return False + # Here's the current algorithm for matching acceptable_aliases: + # + # 1. If the pattern does not have an `@' in it, we first try matching + # it against just the localpart. This was the behavior prior to + # 2.0beta3, and is kept for backwards compatibility. (deprecated). + # + # 2. If that match fails, or the pattern does have an `@' in it, we + # try matching against the entire recip address. + aliases = self.acceptable_aliases.splitlines() + for addr, localpart in recips: + for alias in aliases: + stripped = alias.strip() + if not stripped: + # Ignore blank or empty lines + continue + if '@' not in stripped and domatch(stripped, localpart): + return True + if domatch(stripped, addr): + return True + return False + + def parse_matching_header_opt(self): + """Return a list of triples [(field name, regex, line), ...].""" + # - Blank lines and lines with '#' as first char are skipped. + # - Leading whitespace in the matchexp is trimmed - you can defeat + # that by, eg, containing it in gratuitous square brackets. + all = [] + for line in self.bounce_matching_headers.split('\n'): + line = line.strip() + # Skip blank lines and lines *starting* with a '#'. + if not line or line[0] == "#": + continue + i = line.find(':') + if i < 0: + # This didn't look like a header line. BAW: should do a + # better job of informing the list admin. + syslog('config', 'bad bounce_matching_header line: %s\n%s', + self.real_name, line) + else: + header = line[:i] + value = line[i+1:].lstrip() + try: + cre = re.compile(value, re.IGNORECASE) + except re.error, e: + # The regexp was malformed. BAW: should do a better + # job of informing the list admin. + syslog('config', '''\ +bad regexp in bounce_matching_header line: %s +\n%s (cause: %s)''', self.real_name, value, e) + else: + all.append((header, cre, line)) + return all + + def hasMatchingHeader(self, msg): + """Return true if named header field matches a regexp in the + bounce_matching_header list variable. + + Returns constraint line which matches or empty string for no + matches. + """ + for header, cre, line in self.parse_matching_header_opt(): + for value in msg.get_all(header, []): + if cre.search(value): + return line + return 0 + + def autorespondToSender(self, sender, lang=None): + """Return true if Mailman should auto-respond to this sender. + + This is only consulted for messages sent to the -request address, or + for posting hold notifications, and serves only as a safety value for + mail loops with email 'bots. + """ + # language setting + if lang == None: + lang = self.preferred_language + i18n.set_language(lang) + # No limit + if mm_cfg.MAX_AUTORESPONSES_PER_DAY == 0: + return 1 + today = time.localtime()[:3] + info = self.hold_and_cmd_autoresponses.get(sender) + if info is None or info[0] <> today: + # First time we've seen a -request/post-hold for this sender + self.hold_and_cmd_autoresponses[sender] = (today, 1) + # BAW: no check for MAX_AUTORESPONSES_PER_DAY <= 1 + return 1 + date, count = info + if count < 0: + # They've already hit the limit for today. + syslog('vette', '-request/hold autoresponse discarded for: %s', + sender) + return 0 + if count >= mm_cfg.MAX_AUTORESPONSES_PER_DAY: + syslog('vette', '-request/hold autoresponse limit hit for: %s', + sender) + self.hold_and_cmd_autoresponses[sender] = (today, -1) + # Send this notification message instead + text = Utils.maketext( + 'nomoretoday.txt', + {'sender' : sender, + 'listname': '%s@%s' % (self.real_name, self.host_name), + 'num' : count, + 'owneremail': self.GetOwnerEmail(), + }, + lang=lang) + msg = Message.UserNotification( + sender, self.GetOwnerEmail(), + _('Last autoresponse notification for today'), + text, lang=lang) + msg.send(self) + return 0 + self.hold_and_cmd_autoresponses[sender] = (today, count+1) + return 1 + + def GetBannedPattern(self, email): + """Returns matched entry in ban_list if email matches. + Otherwise returns None. + """ + ban = False + for pattern in self.ban_list: + if pattern.startswith('^'): + # This is a regular expression match + try: + if re.search(pattern, email, re.IGNORECASE): + ban = True + break + except re.error: + # BAW: we should probably remove this pattern + pass + else: + # Do the comparison case insensitively + if pattern.lower() == email.lower(): + ban = True + break + if ban: + return pattern + else: + return None + + + + # + # Multilingual (i18n) support + # + def GetAvailableLanguages(self): + langs = self.available_languages + # If we don't add this, and the site admin has never added any + # language support to the list, then the general admin page may have a + # blank field where the list owner is supposed to chose the list's + # preferred language. + if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs: + langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # When testing, it's possible we've disabled a language, so just + # filter things out so we don't get tracebacks. + return [lang for lang in langs if mm_cfg.LC_DESCRIPTIONS.has_key(lang)] diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Utils.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Utils.py new file mode 100644 index 0000000..819315e --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/Utils.py @@ -0,0 +1,874 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + + +"""Miscellaneous essential routines. + +This includes actual message transmission routines, address checking and +message and address munging, a handy-dandy routine to map a function on all +the mailing lists, and whatever else doesn't belong elsewhere. + +""" + +from __future__ import nested_scopes + +import os +import re +import cgi +import sha +import time +import errno +import base64 +import random +import urlparse +import htmlentitydefs +import email.Header +import email.Iterators +from email.Errors import HeaderParseError +from types import UnicodeType +from string import whitespace, digits +try: + # Python 2.2 + from string import ascii_letters +except ImportError: + # Older Pythons + _lower = 'abcdefghijklmnopqrstuvwxyz' + ascii_letters = _lower + _lower.upper() + +from Mailman import mm_cfg +from Mailman import Errors +from Mailman import Site +from Mailman.SafeDict import SafeDict +from Mailman.Logging.Syslog import syslog + +try: + True, False +except NameError: + True = 1 + False = 0 + +EMPTYSTRING = '' +UEMPTYSTRING = u'' +NL = '\n' +DOT = '.' +IDENTCHARS = ascii_letters + digits + '_' + +# Search for $(identifier)s strings, except that the trailing s is optional, +# since that's a common mistake +cre = re.compile(r'%\(([_a-z]\w*?)\)s?', re.IGNORECASE) +# Search for $$, $identifier, or ${identifier} +dre = re.compile(r'(\${2})|\$([_a-z]\w*)|\${([_a-z]\w*)}', re.IGNORECASE) + + + +def list_exists(listname): + """Return true iff list `listname' exists.""" + # The existance of any of the following file proves the list exists + # : config.pck, config.pck.last, config.db, config.db.last + # + # The former two are for 2.1alpha3 and beyond, while the latter two are + # for all earlier versions. + basepath = Site.get_listpath(listname) + for ext in ('.pck', '.pck.last', '.db', '.db.last'): + dbfile = os.path.join(basepath, 'config' + ext) + if os.path.exists(dbfile): + return True + return False + + +def list_names(): + """Return the names of all lists in default list directory.""" + # We don't currently support separate listings of virtual domains + return Site.get_listnames() + + + +# a much more naive implementation than say, Emacs's fill-paragraph! +def wrap(text, column=70, honor_leading_ws=True): + """Wrap and fill the text to the specified column. + + Wrapping is always in effect, although if it is not possible to wrap a + line (because some word is longer than `column' characters) the line is + broken at the next available whitespace boundary. Paragraphs are also + always filled, unless honor_leading_ws is true and the line begins with + whitespace. This is the algorithm that the Python FAQ wizard uses, and + seems like a good compromise. + + """ + wrapped = '' + # first split the text into paragraphs, defined as a blank line + paras = re.split('\n\n', text) + for para in paras: + # fill + lines = [] + fillprev = False + for line in para.split(NL): + if not line: + lines.append(line) + continue + if honor_leading_ws and line[0] in whitespace: + fillthis = False + else: + fillthis = True + if fillprev and fillthis: + # if the previous line should be filled, then just append a + # single space, and the rest of the current line + lines[-1] = lines[-1].rstrip() + ' ' + line + else: + # no fill, i.e. retain newline + lines.append(line) + fillprev = fillthis + # wrap each line + for text in lines: + while text: + if len(text) <= column: + line = text + text = '' + else: + bol = column + # find the last whitespace character + while bol > 0 and text[bol] not in whitespace: + bol -= 1 + # now find the last non-whitespace character + eol = bol + while eol > 0 and text[eol] in whitespace: + eol -= 1 + # watch out for text that's longer than the column width + if eol == 0: + # break on whitespace after column + eol = column + while eol < len(text) and text[eol] not in whitespace: + eol += 1 + bol = eol + while bol < len(text) and text[bol] in whitespace: + bol += 1 + bol -= 1 + line = text[:eol+1] + '\n' + # find the next non-whitespace character + bol += 1 + while bol < len(text) and text[bol] in whitespace: + bol += 1 + text = text[bol:] + wrapped += line + wrapped += '\n' + # end while text + wrapped += '\n' + # end for text in lines + # the last two newlines are bogus + return wrapped[:-2] + + + +def QuotePeriods(text): + JOINER = '\n .\n' + SEP = '\n.\n' + return JOINER.join(text.split(SEP)) + + +# This takes an email address, and returns a tuple containing (user,host) +def ParseEmail(email): + user = None + domain = None + email = email.lower() + at_sign = email.find('@') + if at_sign < 1: + return email, None + user = email[:at_sign] + rest = email[at_sign+1:] + domain = rest.split('.') + return user, domain + + +def LCDomain(addr): + "returns the address with the domain part lowercased" + atind = addr.find('@') + if atind == -1: # no domain part + return addr + return addr[:atind] + '@' + addr[atind+1:].lower() + + +# TBD: what other characters should be disallowed? +_badchars = re.compile(r'[][()<>|;^,\000-\037\177-\377]') + +def ValidateEmail(s): + """Verify that an email address isn't grossly evil.""" + # Pretty minimal, cheesy check. We could do better... + if not s or s.count(' ') > 0: + raise Errors.MMBadEmailError + if _badchars.search(s) or s[0] == '-': + raise Errors.MMHostileAddress, s + user, domain_parts = ParseEmail(s) + # This means local, unqualified addresses, are no allowed + if not domain_parts: + raise Errors.MMBadEmailError, s + if len(domain_parts) < 2: + raise Errors.MMBadEmailError, s + + + +# Patterns which may be used to form malicious path to inject a new +# line in the mailman error log. (TK: advisory by Moritz Naumann) +CRNLpat = re.compile(r'[^\x21-\x7e]') + +def GetPathPieces(envar='PATH_INFO'): + path = os.environ.get(envar) + if path: + if CRNLpat.search(path): + path = CRNLpat.split(path)[0] + syslog('error', 'Warning: Possible malformed path attack.') + return [p for p in path.split('/') if p] + return None + + + +def ScriptURL(target, web_page_url=None, absolute=False): + """target - scriptname only, nothing extra + web_page_url - the list's configvar of the same name + absolute - a flag which if set, generates an absolute url + """ + if web_page_url is None: + web_page_url = mm_cfg.DEFAULT_URL_PATTERN % get_domain() + if web_page_url[-1] <> '/': + web_page_url = web_page_url + '/' + fullpath = os.environ.get('REQUEST_URI') + if fullpath is None: + fullpath = os.environ.get('SCRIPT_NAME', '') + \ + os.environ.get('PATH_INFO', '') + baseurl = urlparse.urlparse(web_page_url)[2] + if not absolute and fullpath.endswith(baseurl): + # Use relative addressing + fullpath = fullpath[len(baseurl):] + i = fullpath.find('?') + if i > 0: + count = fullpath.count('/', 0, i) + else: + count = fullpath.count('/') + path = ('../' * count) + target + else: + path = web_page_url + target + return path + mm_cfg.CGIEXT + + + +def GetPossibleMatchingAddrs(name): + """returns a sorted list of addresses that could possibly match + a given name. + + For Example, given scott@pobox.com, return ['scott@pobox.com'], + given scott@blackbox.pobox.com return ['scott@blackbox.pobox.com', + 'scott@pobox.com']""" + + name = name.lower() + user, domain = ParseEmail(name) + res = [name] + if domain: + domain = domain[1:] + while len(domain) >= 2: + res.append("%s@%s" % (user, DOT.join(domain))) + domain = domain[1:] + return res + + + +def List2Dict(L, foldcase=False): + """Return a dict keyed by the entries in the list passed to it.""" + d = {} + if foldcase: + for i in L: + d[i.lower()] = True + else: + for i in L: + d[i] = True + return d + + + +_vowels = ('a', 'e', 'i', 'o', 'u') +_consonants = ('b', 'c', 'd', 'f', 'g', 'h', 'k', 'm', 'n', + 'p', 'r', 's', 't', 'v', 'w', 'x', 'z') +_syllables = [] + +for v in _vowels: + for c in _consonants: + _syllables.append(c+v) + _syllables.append(v+c) +del c, v + +def UserFriendly_MakeRandomPassword(length): + syls = [] + while len(syls) * 2 < length: + syls.append(random.choice(_syllables)) + return EMPTYSTRING.join(syls)[:length] + + +def Secure_MakeRandomPassword(length): + bytesread = 0 + bytes = [] + fd = None + try: + while bytesread < length: + try: + # Python 2.4 has this on available systems. + newbytes = os.urandom(length - bytesread) + except (AttributeError, NotImplementedError): + if fd is None: + try: + fd = os.open('/dev/urandom', os.O_RDONLY) + except OSError, e: + if e.errno <> errno.ENOENT: + raise + # We have no available source of cryptographically + # secure random characters. Log an error and fallback + # to the user friendly passwords. + syslog('error', + 'urandom not available, passwords not secure') + return UserFriendly_MakeRandomPassword(length) + newbytes = os.read(fd, length - bytesread) + bytes.append(newbytes) + bytesread += len(newbytes) + s = base64.encodestring(EMPTYSTRING.join(bytes)) + # base64 will expand the string by 4/3rds + return s.replace('\n', '')[:length] + finally: + if fd is not None: + os.close(fd) + + +def MakeRandomPassword(length=mm_cfg.MEMBER_PASSWORD_LENGTH): + if mm_cfg.USER_FRIENDLY_PASSWORDS: + return UserFriendly_MakeRandomPassword(length) + return Secure_MakeRandomPassword(length) + + +def GetRandomSeed(): + chr1 = int(random.random() * 52) + chr2 = int(random.random() * 52) + def mkletter(c): + if 0 <= c < 26: + c += 65 + if 26 <= c < 52: + #c = c - 26 + 97 + c += 71 + return c + return "%c%c" % tuple(map(mkletter, (chr1, chr2))) + + + +def set_global_password(pw, siteadmin=True): + if siteadmin: + filename = mm_cfg.SITE_PW_FILE + else: + filename = mm_cfg.LISTCREATOR_PW_FILE + # rw-r----- + omask = os.umask(026) + try: + fp = open(filename, 'w') + fp.write(sha.new(pw).hexdigest() + '\n') + fp.close() + finally: + os.umask(omask) + + +def get_global_password(siteadmin=True): + if siteadmin: + filename = mm_cfg.SITE_PW_FILE + else: + filename = mm_cfg.LISTCREATOR_PW_FILE + try: + fp = open(filename) + 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 None + return challenge + + +def check_global_password(response, siteadmin=True): + challenge = get_global_password(siteadmin) + if challenge is None: + return None + return challenge == sha.new(response).hexdigest() + + + +def websafe(s): + return cgi.escape(s, quote=True) + + +def nntpsplit(s): + parts = s.split(':', 1) + if len(parts) == 2: + try: + return parts[0], int(parts[1]) + except ValueError: + pass + # Use the defaults + return s, 119 + + + +# Just changing these two functions should be enough to control the way +# that email address obscuring is handled. +def ObscureEmail(addr, for_text=False): + """Make email address unrecognizable to web spiders, but invertable. + + When for_text option is set (not default), make a sentence fragment + instead of a token.""" + if for_text: + return addr.replace('@', ' at ') + else: + return addr.replace('@', '--at--') + +def UnobscureEmail(addr): + """Invert ObscureEmail() conversion.""" + # Contrived to act as an identity operation on already-unobscured + # emails, so routines expecting obscured ones will accept both. + return addr.replace('--at--', '@') + + + +class OuterExit(Exception): + pass + +def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): + # Make some text from a template file. The order of searches depends on + # whether mlist and lang are provided. Once the templatefile is found, + # string substitution is performed by interpolation in `dict'. If `raw' + # is false, the resulting text is wrapped/filled by calling wrap(). + # + # When looking for a template in a specific language, there are 4 places + # that are searched, in this order: + # + # 1. the list-specific language directory + # lists// + # + # 2. the domain-specific language directory + # templates// + # + # 3. the site-wide language directory + # templates/site/ + # + # 4. the global default language directory + # templates/ + # + # The first match found stops the search. In this way, you can specialize + # templates at the desired level, or, if you use only the default + # templates, you don't need to change anything. You should never modify + # files in the templates/ subdirectory, since Mailman will + # overwrite these when you upgrade. That's what the templates/site + # language directories are for. + # + # A further complication is that the language to search for is determined + # by both the `lang' and `mlist' arguments. The search order there is + # that if lang is given, then the 4 locations above are searched, + # substituting lang for . If no match is found, and mlist is + # given, then the 4 locations are searched using the list's preferred + # language. After that, the server default language is used for + # . If that still doesn't yield a template, then the standard + # distribution's English language template is used as an ultimate + # fallback. If that's missing you've got big problems. ;) + # + # A word on backwards compatibility: Mailman versions prior to 2.1 stored + # templates in templates/*.{html,txt} and lists//*.{html,txt}. + # Those directories are no longer searched so if you've got customizations + # in those files, you should move them to the appropriate directory based + # on the above description. Mailman's upgrade script cannot do this for + # you. + # + # The function has been revised and renamed as it now returns both the + # template text and the path from which it retrieved the template. The + # original function is now a wrapper which just returns the template text + # as before, by calling this renamed function and discarding the second + # item returned. + # + # Calculate the languages to scan + languages = [] + if lang is not None: + languages.append(lang) + if mlist is not None: + languages.append(mlist.preferred_language) + languages.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # Calculate the locations to scan + searchdirs = [] + if mlist is not None: + searchdirs.append(mlist.fullpath()) + searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, mlist.host_name)) + searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, 'site')) + searchdirs.append(mm_cfg.TEMPLATE_DIR) + # Start scanning + fp = None + try: + for lang in languages: + for dir in searchdirs: + filename = os.path.join(dir, lang, templatefile) + try: + fp = open(filename) + raise OuterExit + except IOError, e: + if e.errno <> errno.ENOENT: raise + # Okay, it doesn't exist, keep looping + fp = None + except OuterExit: + pass + if fp is None: + # Try one last time with the distro English template, which, unless + # you've got a really broken installation, must be there. + try: + filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) + fp = open(filename) + except IOError, e: + if e.errno <> errno.ENOENT: raise + # We never found the template. BAD! + raise IOError(errno.ENOENT, 'No template file found', templatefile) + template = fp.read() + fp.close() + text = template + if dict is not None: + try: + sdict = SafeDict(dict) + try: + text = sdict.interpolate(template) + except UnicodeError: + # Try again after coercing the template to unicode + utemplate = unicode(template, GetCharSet(lang), 'replace') + text = sdict.interpolate(utemplate) + except (TypeError, ValueError), e: + # The template is really screwed up + syslog('error', 'broken template: %s\n%s', filename, e) + pass + if raw: + return text, filename + return wrap(text), filename + + +def maketext(templatefile, dict=None, raw=False, lang=None, mlist=None): + return findtext(templatefile, dict, raw, lang, mlist)[0] + + + +ADMINDATA = { + # admin keyword: (minimum #args, maximum #args) + 'confirm': (1, 1), + 'help': (0, 0), + 'info': (0, 0), + 'lists': (0, 0), + 'options': (0, 0), + 'password': (2, 2), + 'remove': (0, 0), + 'set': (3, 3), + 'subscribe': (0, 3), + 'unsubscribe': (0, 1), + 'who': (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 is_administrivia(msg): + linecnt = 0 + lines = [] + for line in email.Iterators.body_line_iterator(msg): + # Strip out any signatures + if line == '-- ': + break + if line.strip(): + linecnt += 1 + if linecnt > mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES: + return False + 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 True + # Look at the first N lines and see if there is any administrivia on the + # line. BAW: N is currently hardcoded to 5. str-ify the Subject: header + # because it may be an email.Header.Header instance rather than a string. + bodylines = lines[:5] + subject = str(msg.get('subject', '')) + bodylines.append(subject) + for line in bodylines: + if not line.strip(): + continue + 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 True + return False + + + +def GetRequestURI(fallback=None, escape=True): + """Return the full virtual path this CGI script was invoked with. + + Newer web servers seems to supply this info in the REQUEST_URI + environment variable -- which isn't part of the CGI/1.1 spec. + Thus, if REQUEST_URI isn't available, we concatenate SCRIPT_NAME + and PATH_INFO, both of which are part of CGI/1.1. + + Optional argument `fallback' (default `None') is returned if both of + the above methods fail. + + The url will be cgi escaped to prevent cross-site scripting attacks, + unless `escape' is set to 0. + """ + url = fallback + if os.environ.has_key('REQUEST_URI'): + url = os.environ['REQUEST_URI'] + elif os.environ.has_key('SCRIPT_NAME') and os.environ.has_key('PATH_INFO'): + url = os.environ['SCRIPT_NAME'] + os.environ['PATH_INFO'] + if escape: + return websafe(url) + return url + + +# Wait on a dictionary of child pids +def reap(kids, func=None, once=False): + while kids: + if func: + func() + try: + pid, status = os.waitpid(-1, os.WNOHANG) + except OSError, e: + # If the child procs had a bug we might have no children + if e.errno <> errno.ECHILD: + raise + kids.clear() + break + if pid <> 0: + try: + del kids[pid] + except KeyError: + # Huh? How can this happen? + pass + if once: + break + + +def GetLanguageDescr(lang): + return mm_cfg.LC_DESCRIPTIONS[lang][0] + + +def GetCharSet(lang): + return mm_cfg.LC_DESCRIPTIONS[lang][1] + +def IsLanguage(lang): + return mm_cfg.LC_DESCRIPTIONS.has_key(lang) + + + +def get_domain(): + host = os.environ.get('HTTP_HOST', os.environ.get('SERVER_NAME')) + port = os.environ.get('SERVER_PORT') + # Strip off the port if there is one + if port and host.endswith(':' + port): + host = host[:-len(port)-1] + if mm_cfg.VIRTUAL_HOST_OVERVIEW and host: + return host.lower() + else: + # See the note in Defaults.py concerning DEFAULT_URL + # vs. DEFAULT_URL_HOST. + hostname = ((mm_cfg.DEFAULT_URL + and urlparse.urlparse(mm_cfg.DEFAULT_URL)[1]) + or mm_cfg.DEFAULT_URL_HOST) + return hostname.lower() + + +def get_site_email(hostname=None, extra=None): + if hostname is None: + hostname = mm_cfg.VIRTUAL_HOSTS.get(get_domain(), get_domain()) + if extra is None: + return '%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, hostname) + return '%s-%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, extra, hostname) + + + +# This algorithm crafts a guaranteed unique message-id. The theory here is +# that pid+listname+host will distinguish the message-id for every process on +# the system, except when process ids wrap around. To further distinguish +# message-ids, we prepend the integral time in seconds since the epoch. It's +# still possible that we'll vend out more than one such message-id per second, +# so we prepend a monotonically incrementing serial number. It's highly +# unlikely that within a single second, there'll be a pid wraparound. +_serial = 0 +def unique_message_id(mlist): + global _serial + msgid = '' % ( + _serial, time.time(), os.getpid(), + mlist.internal_name(), mlist.host_name) + _serial += 1 + return msgid + + +# Figure out epoch seconds of midnight at the start of today (or the given +# 3-tuple date of (year, month, day). +def midnight(date=None): + if date is None: + date = time.localtime()[:3] + # -1 for dst flag tells the library to figure it out + return time.mktime(date + (0,)*5 + (-1,)) + + + +# Utilities to convert from simplified $identifier substitutions to/from +# standard Python $(identifier)s substititions. The "Guido rules" for the +# former are: +# $$ -> $ +# $identifier -> $(identifier)s +# ${identifier} -> $(identifier)s + +def to_dollar(s): + """Convert from %-strings to $-strings.""" + s = s.replace('$', '$$').replace('%%', '%') + parts = cre.split(s) + for i in range(1, len(parts), 2): + if parts[i+1] and parts[i+1][0] in IDENTCHARS: + parts[i] = '${' + parts[i] + '}' + else: + parts[i] = '$' + parts[i] + return EMPTYSTRING.join(parts) + + +def to_percent(s): + """Convert from $-strings to %-strings.""" + s = s.replace('%', '%%').replace('$$', '$') + parts = dre.split(s) + for i in range(1, len(parts), 4): + if parts[i] is not None: + parts[i] = '$' + elif parts[i+1] is not None: + parts[i+1] = '%(' + parts[i+1] + ')s' + else: + parts[i+2] = '%(' + parts[i+2] + ')s' + return EMPTYSTRING.join(filter(None, parts)) + + +def dollar_identifiers(s): + """Return the set (dictionary) of identifiers found in a $-string.""" + d = {} + for name in filter(None, [b or c or None for a, b, c in dre.findall(s)]): + d[name] = True + return d + + +def percent_identifiers(s): + """Return the set (dictionary) of identifiers found in a %-string.""" + d = {} + for name in cre.findall(s): + d[name] = True + return d + + + +# Utilities to canonicalize a string, which means un-HTML-ifying the string to +# produce a Unicode string or an 8-bit string if all the characters are ASCII. +def canonstr(s, lang=None): + newparts = [] + parts = re.split(r'&(?P[^;]+);', s) + def appchr(i): + if i < 256: + newparts.append(chr(i)) + else: + newparts.append(unichr(i)) + while True: + newparts.append(parts.pop(0)) + if not parts: + break + ref = parts.pop(0) + if ref.startswith('#'): + try: + appchr(int(ref[1:])) + except ValueError: + # Non-convertable, stick with what we got + newparts.append('&'+ref+';') + else: + c = htmlentitydefs.entitydefs.get(ref, '?') + if c.startswith('#') and c.endswith(';'): + appchr(int(ref[1:-1])) + else: + newparts.append(c) + newstr = EMPTYSTRING.join(newparts) + if isinstance(newstr, UnicodeType): + return newstr + # We want the default fallback to be iso-8859-1 even if the language is + # English (us-ascii). This seems like a practical compromise so that + # non-ASCII characters in names can be used in English lists w/o having to + # change the global charset for English from us-ascii (which I + # superstitiously think may have unintended consequences). + if lang is None: + charset = 'iso-8859-1' + else: + charset = GetCharSet(lang) + if charset == 'us-ascii': + charset = 'iso-8859-1' + return unicode(newstr, charset, 'replace') + + +# The opposite of canonstr() -- sorta. I.e. it attempts to encode s in the +# charset of the given language, which is the character set that the page will +# be rendered in, and failing that, replaces non-ASCII characters with their +# html references. It always returns a byte string. +def uncanonstr(s, lang=None): + if s is None: + s = u'' + if lang is None: + charset = 'us-ascii' + else: + charset = GetCharSet(lang) + # See if the string contains characters only in the desired character + # set. If so, return it unchanged, except for coercing it to a byte + # string. + try: + if isinstance(s, UnicodeType): + return s.encode(charset) + else: + u = unicode(s, charset) + return s + except UnicodeError: + # Nope, it contains funny characters, so html-ref it + return uquote(s) + + +def uquote(s): + a = [] + for c in s: + o = ord(c) + if o > 127: + a.append('&#%3d;' % o) + else: + a.append(c) + # Join characters together and coerce to byte string + return str(EMPTYSTRING.join(a)) + + +def oneline(s, cset): + # Decode header string in one line and convert into specified charset + try: + h = email.Header.make_header(email.Header.decode_header(s)) + ustr = h.__unicode__() + line = UEMPTYSTRING.join(ustr.splitlines()) + return line.encode(cset, 'replace') + except (LookupError, UnicodeError, ValueError, HeaderParseError): + # possibly charset problem. return with undecoded string in one line. + return EMPTYSTRING.join(s.splitlines()) diff --git a/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/htmlformat.py b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/htmlformat.py new file mode 100644 index 0000000..10fb601 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Mailman/2.1.9-4.el5/Mailman/htmlformat.py @@ -0,0 +1,779 @@ +# Copyright (C) 1998-2006 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + + +"""Library for program-based construction of an HTML documents. + +Encapsulate HTML formatting directives in classes that act as containers +for python and, recursively, for nested HTML formatting objects. +""" + + +# Eventually could abstract down to HtmlItem, which outputs an arbitrary html +# object given start / end tags, valid options, and a value. Ug, objects +# shouldn't be adding their own newlines. The next object should. + + +import types +import cgi +import os + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _ + +SPACE = ' ' +EMPTYSTRING = '' +NL = '\n' + +# Format an arbitrary object. +def HTMLFormatObject(item, indent): + "Return a peesentation of an object, invoking their Format method if any." + if type(item) == type(''): + return item + elif not hasattr(item, "Format"): + return `item` + else: + return item.Format(indent) + +def CaseInsensitiveKeyedDict(d): + result = {} + for (k,v) in d.items(): + result[k.lower()] = v + return result + +# Given references to two dictionaries, copy the second dictionary into the +# first one. +def DictMerge(destination, fresh_dict): + for (key, value) in fresh_dict.items(): + destination[key] = value + +class Table: + def __init__(self, **table_opts): + self.cells = [] + self.cell_info = {} + self.row_info = {} + self.opts = table_opts + + def AddOptions(self, opts): + DictMerge(self.opts, opts) + + # Sets all of the cells. It writes over whatever cells you had there + # previously. + + def SetAllCells(self, cells): + self.cells = cells + + # Add a new blank row at the end + def NewRow(self): + self.cells.append([]) + + # Add a new blank cell at the end + def NewCell(self): + self.cells[-1].append('') + + def AddRow(self, row): + self.cells.append(row) + + def AddCell(self, cell): + self.cells[-1].append(cell) + + def AddCellInfo(self, row, col, **kws): + kws = CaseInsensitiveKeyedDict(kws) + if not self.cell_info.has_key(row): + self.cell_info[row] = { col : kws } + elif self.cell_info[row].has_key(col): + DictMerge(self.cell_info[row], kws) + else: + self.cell_info[row][col] = kws + + def AddRowInfo(self, row, **kws): + kws = CaseInsensitiveKeyedDict(kws) + if not self.row_info.has_key(row): + self.row_info[row] = kws + else: + DictMerge(self.row_info[row], kws) + + # What's the index for the row we just put in? + def GetCurrentRowIndex(self): + return len(self.cells)-1 + + # What's the index for the col we just put in? + def GetCurrentCellIndex(self): + return len(self.cells[-1])-1 + + def ExtractCellInfo(self, info): + valid_mods = ['rowspan', 'colspan', 'css'] + output = '' + for (key, val) in info.items(): + if not key in valid_mods: + continue + if key == 'css': + output = output + ' %s' % val + else: + output = output + ' %s="%s"' % (key, val) + return output + + def ExtractRowInfo(self, info): + valid_mods = ['css'] + output = '' + for (key, val) in info.items(): + if not key in valid_mods: + continue + output = output + ' %s' % val + return output + + def ExtractTableInfo(self, info): + valid_mods = ['css'] + output = '' + for (key, val) in info.items(): + if not key in valid_mods: + continue + output = output + ' %s' % val + return output + + def FormatCell(self, row, col, indent): + try: + my_info = self.cell_info[row][col] + except: + my_info = None + + output = '\n' + ' '*indent + '' + + for i in range(len(self.cells[row])): + output = output + self.FormatCell(row, i, indent + 2) + + output = output + '\n' + ' '*indent + '' + + return output + + def Format(self, indent=0): + output = '\n' + ' '*indent + '' + + for i in range(len(self.cells)): + output = output + self.FormatRow(i, indent + 2) + + output = output + '\n' + ' '*indent + '
      \n' + + return output + + +class Link: + def __init__(self, href, text, target=None): + self.href = href + self.text = text + self.target = target + + def Format(self, indent=0): + texpr = "" + if self.target != None: + texpr = ' target="%s"' % self.target + return '%s' % (HTMLFormatObject(self.href, indent), + texpr, + HTMLFormatObject(self.text, indent)) + +class FontSize: + """FontSize is being deprecated - use FontAttr(..., size="...") instead.""" + def __init__(self, size, *items): + self.items = list(items) + self.size = size + + def Format(self, indent=0): + output = '' % self.size + for item in self.items: + output = output + HTMLFormatObject(item, indent) + output = output + '' + return output + +class FontAttr: + """Present arbitrary font attributes.""" + def __init__(self, *items, **kw): + self.items = list(items) + self.attrs = kw + + def Format(self, indent=0): + seq = [] + for k, v in self.attrs.items(): + seq.append('%s="%s"' % (k, v)) + output = '' % SPACE.join(seq) + for item in self.items: + output = output + HTMLFormatObject(item, indent) + output = output + '' + return output + + +class Container: + def __init__(self, *items): + if not items: + self.items = [] + else: + self.items = items + + def AddItem(self, obj): + self.items.append(obj) + + def addMessage(self, errmsg, tag=None, css=None): + if css is None: + css = 'class="message"' + self.AddItem(_('\n
      ')) + if tag is None: + self.AddItem(Paragraph(errmsg)) + else: + self.AddItem(Header(3, tag).Format() + Paragraph(errmsg).Format()) + self.AddItem('
      ') + + def Format(self, indent=0): + output = [] + for item in self.items: + output.append(HTMLFormatObject(item, indent)) + return EMPTYSTRING.join(output) + + +class Label(Container): + align = 'right' + + def __init__(self, *items): + Container.__init__(self, *items) + + def Format(self, indent=0): + return ('
      ' % self.align) + \ + Container.Format(self, indent) + \ + '
      ' + +# My own standard document template. YMMV. +# something more abstract would be more work to use... + +class Document(Container): + title = None + language = None + bgcolor = mm_cfg.WEB_BG_COLOR + suppress_head = 0 + + def set_language(self, lang=None): + self.language = lang + + def set_bgcolor(self, color): + self.bgcolor = color + + def SetTitle(self, title): + self.title = title + + def Format(self, indent=0, **kws): + charset = 'us-ascii' + if self.language: + charset = Utils.GetCharSet(self.language) + output = ['Content-Type: text/html; charset=%s\n' % charset] + if not self.suppress_head: + kws.setdefault('bgcolor', self.bgcolor) + tab = ' ' * indent + output.extend(['', + '', + '', + '', + ]) + if mm_cfg.IMAGE_LOGOS: + output.append('' % + (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) + # Hit all the bases + output.append('' % charset) + if self.title: + output.append('%s%s' % (tab, self.title)) + output.append('') + output.append('') + output.append('%s' % tab) + quals = [] + # Default link colors + if mm_cfg.WEB_VLINK_COLOR: + kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR) + if mm_cfg.WEB_ALINK_COLOR: + kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR) + if mm_cfg.WEB_LINK_COLOR: + kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) + for k, v in kws.items(): + quals.append('%s="%s"' % (k, v)) + + listadmin_link = Link(Utils.ScriptURL('admin'), _('Administration')).Format() + listinfo_link = Link(Utils.ScriptURL('listinfo'), _('General Information')).Format() + siteowner = Utils.get_site_email() + adminmail_link = Link('mailto:' + siteowner, siteowner).Format() + output.extend([''' + + + + +
      + Lists +

      ''' + _('%(listinfo_link)s | %(listadmin_link)s') + '''

      +

      ''' + _('Mailing Lists') + '''

      +
      + + +

      + +

      + +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/home.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/home.php new file mode 100755 index 0000000..4d10121 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/home.php @@ -0,0 +1,58 @@ + + +
    +

    +
    +
    + + + + + + + + + + + + + fetch()) : ?> + + + + + + + + + + +
    + +
    +
    +
    \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/main.php new file mode 100755 index 0000000..f40388a --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/main.php @@ -0,0 +1,117 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + + + + + + +
    +
    + + + +
    +
    + +
    +

    +
    +
    + %s', '
  • %s
  • ', '
  • %s
  • ') ?> + +
    + +
    +

    + %s', '
  • %s
  • '); ?> +
    + +
    +
    + + + + %s

    '); ?> + + + + + + + + + + + + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/rss.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/rss.php new file mode 100755 index 0000000..8398f13 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/rss.php @@ -0,0 +1,42 @@ + + + + <?php tpl::headTitlePage() ?> - <?php tpl::lang('Articles') ?> + + ]]> + + Puntal 2 + + fetch()) : ?> + + <?php articles::articleRssTitle() ?> + + + + + + + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/search_results.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/search_results.php new file mode 100755 index 0000000..d547eb2 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/articles/search_results.php @@ -0,0 +1,44 @@ + + + +isEmpty()) : ?> + +

    + + fetch()) : ?> + +

    +
    + '.tpl::lang('Read more',true).''); ?> +
    + + +
    + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/blog.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/blog.css new file mode 100755 index 0000000..8925f26 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/blog.css @@ -0,0 +1,28 @@ + +/* Titres +----------------------------------------------------------*/ +#puntal_content #blogTitle span { + font-weight: bold; +} +.cal { + font-size : 75%; + font-variant: small-caps; + font-weight: bold; +} +.cal table { + border-spacing: 0; + border-collapse: separate; + caption-side: top; +} +.day-date { + text-align: right; + font-style: italic; + font-weight: bold; +} +.post-title { + margin-bottom: 0; +} +.post-info { + margin-top: 0; + font-size: 0.9em; +} \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/form.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/form.php new file mode 100755 index 0000000..7e4538c --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/form.php @@ -0,0 +1,83 @@ + + +

    '.tpl::lang('Error', true).'

    %s'); ?> +%s

    '); ?> + +
    +
    +
    +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    +generatePtb('c_content'); +?>
    + +

    +
    +
    + +

    +
    +
    +
    +

    +

    + +

    + + +

    +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/list.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/list.php new file mode 100755 index 0000000..033cec6 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/list.php @@ -0,0 +1,63 @@ + + + + +fetch()) : ?> + %s

    '); ?> + +
    +

    +
    +
    + + +
    > + Lire la suite

    '); ?> +
    + + +
    +
    +
    + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/main.php new file mode 100755 index 0000000..b4dec10 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/main.php @@ -0,0 +1,96 @@ + + + + + + + + + <?php blog::dcSinglePostTitle('%s - '); blog::dcSingleCatTitle('%s - '); + blog::dcSingleMonthTitle('%s - '); blog::dcCustomTitle('%s - '); tpl::infos(); ?> + + + + + + + + + +
    +
    + + + +
    +
    + + +
    +

    +
    +
    +

    + +
    +
    +
    + + +

    +
    +
    + +
    +
    + + + + + + + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/post.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/post.php new file mode 100755 index 0000000..8b876bd --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/post.php @@ -0,0 +1,118 @@ + + +
    +

    +
    +
    + + + %s
    '); ?> +
    +
    +
    + + +
    +
    +
    +

    + isEmpty()) : /* Message si aucune trackback */?> +

    + + + fetch()) : /* Liste des trackbacks */ + // On met le num�ro du trackback dans une variable + $tb_num = $trackbacks->int_index+1; + ?> +

    + . + , +

    + + +
    + +
    + + + + +

    +

    + +

    + +
    +
    +
    + +
    +
    +
    +

    + isEmpty()) : /* Message si aucune commentaire */ ?> +

    + + + fetch()) : /* Boucle de commentaires */ + // On met le num�ro du commentaire dans une variable + $co_num = $comments->int_index+1; + ?> +

    + . + , +

    + + +
    + +
    + + +

    + + + + + +

    + +
    +
    +
    + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/search_results.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/search_results.php new file mode 100755 index 0000000..bcd116c --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/blog/search_results.php @@ -0,0 +1,60 @@ +isEmpty()) : ?> +

    + + +fetch()) : ?> + +

    + + + +
    > + Lire la suite

    '); ?> +
    + + + + +
    + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/bugtracker.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/bugtracker.css new file mode 100755 index 0000000..cbbf56a --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/bugtracker.css @@ -0,0 +1,74 @@ + +/* Titres +----------------------------------------------------------*/ +#puntal_content #bugTrackerTitle span { + font-weight: bold; +} + +#puntal_content #bugsFilter { + border: none; + font-weight: normal; + width: auto; +} + +/* Menu +----------------------------------------------------------*/ +#bugtrackerMenu { + list-style-type: none; + padding-left: 0; +} +#bugtrackerMenu li { + display: inline; + margin-right: 1em; +} + +/* S�v�rit�s +----------------------------------------------------------*/ +table.bugs td.sev_1 { background-color: #fff5dd; } +table.bugs td.sev_2 { background-color: #ecdbb7; } +table.bugs td.sev_3 { background-color: #f5d5c6; } +table.bugs td.sev_4 { background-color: #F7B390; } +table.bugs td.sev_5 { background-color: #f3a29b; } + +#puntal_content .bugTitle { + width: auto; + border: none; + margin: 0; + font-weight: normal; +} + + +/* Barre d'avancement +----------------------------------------------------------*/ +div.av_bar_ext { + border: 1px solid #004925; + background-color: #fff; + width: 100px; + height: 10px; + text-align: left; +} +div.av_bar_int { + background-color: #060; + height: 10px; +} +p.percent_txt { + margin: 0; + padding: 0; + display: inline; +} + + +/* Commentaires +----------------------------------------------------------*/ +.addLinkTop { + text-align: right; + margin-bottom: 0; +} +.addLinkBottom { + text-align: right; + margin-top: 0; +} + +.comment { + margin: 0 0 1em 0; +} diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/form_add.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/form_add.php new file mode 100755 index 0000000..4cd75f4 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/form_add.php @@ -0,0 +1,109 @@ + + + + +
    +

    +
    +
    + %s', '
  • %s
  • ') ?> +
    +
    +
    + + + + +
    +

    +
    +
    + +
    +
    +
    + + +
    +

    +
    +
    +
    +
    +
    + + +

    +

    + +

    +

    + + +

    +

    + +
    +

    + + +

    + +

    +

    +
    + +

    +

    + +

    +

    +generatePtb('neo_desc'); +?> +
    +
    +
    +

    + + + +

    +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/list.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/list.php new file mode 100755 index 0000000..81b6ebf --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/list.php @@ -0,0 +1,71 @@ + + +
    +

    +
    +
    +
    +
    + +
    +

    +

    + +

    +

    +
    +
    +
    +

    +

    +
    +
    +
    + +
    +
    + +
    +
    + +
    +

    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/main.php new file mode 100755 index 0000000..7feb243 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/main.php @@ -0,0 +1,122 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + + + + + + + + +
    +
    + + + +
    +
    + +
    +

    +
    +
    +
      + %s', + '
    • %s
    • ' ) ?> + + %s', + '
    • %s
    • ' ) ?> +
    +
    +
    +
    + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/search_results.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/search_results.php new file mode 100755 index 0000000..74c6504 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/search_results.php @@ -0,0 +1,42 @@ + + + +isEmpty()) : ?> + +

    + + fetch()) : ?> + +

    +
    + + + +
    + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/viewbug.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/viewbug.php new file mode 100755 index 0000000..8e22f7f --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/bugtracker/viewbug.php @@ -0,0 +1,64 @@ + + +
    +

    +
    +
    + +

    +

    / %

    +
    +
    +
    +
    +
    + + +

    + +
    +

    +
    +
    + + fetch()) : ?> +

    -

    +
    + + +

    + +
    +
    +
    + +

    + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/add_event.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/add_event.php new file mode 100755 index 0000000..58947d8 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/add_event.php @@ -0,0 +1,162 @@ + + +
    +

    +
    +
    +

    + %s', '
  • %s
  • '); ?> +
    +
    +
    + + + + +
    +

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + +
    +

    +
    +
    + +
    +
    + +

    + +

    +
    +
    + +
    +
    + +
    +

    + + + + + +

    +
    +
    +
    +
    +
    + +
    + +

    +

    + +

    +

    + +
    + + +

    + +

    + +

    +

    + +

    +

    +generatePtb('req_message'); +?> + +
    +
    +
    + 0) : ?> + +
    +
    + +
    +
    + + + + + + + + +
    +
    +
    +
    + + +

    + + + +

    +
    +
    +
    \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/calendar.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/calendar.css new file mode 100755 index 0000000..f34fbb6 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/calendar.css @@ -0,0 +1,61 @@ +#puntal_content #calendarTitle span { + font-weight: bold; +} + +/* tableaux */ +table.calendar { +} +#puntal_content table.minicalendar { + float: left; + width: 49%; +} +#puntal_content table.minicalendar caption { +} + +/* bloc de texte */ +div#infos_cal { + float: right; + width: 60%; + margin-top: 1em; +} +div#infos_cal h3 { + margin-top: 1em; +} + +/* cellules */ +table.calendar td { + height: 75px; + width: 14%; + vertical-align: top; +} +table.calendar td.inactive { + background: #ddd; +} +table.calendar td.past { + color: #ccc; +} +table.calendar td.today { + background: #fff; +} +table.calendar td.active { + border: 1px solid #c00; +} + +/* liens */ +table.calendar td.past a { + color: #ccc; +} +table.calendar td.today a {} +table.calendar td.active a {} + +/* numero jour */ +table.calendar td p.daynumber {} + +/* liste �v�nement dans cellules */ +table.calendar td ul.eventlist { + font-size: 0.9em; +} +table.calendar td ul.eventlist li { + list-style: square inside +} + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/edit_event.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/edit_event.php new file mode 100755 index 0000000..d0259b8 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/edit_event.php @@ -0,0 +1,157 @@ + + +
    +

    +
    +
    +

    +
      + %s
    ', '
  • %s
  • '); + ?> + +
    +
    +
    + + + + +
    +

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +

    +
    +
    + +
    +
    + +

    + +

    +
    +
    + +
    +
    + +
    + + + + + + +
    +
    +
    +
    +
    + +
    + + + + +
    + + + +generatePtb('req_message'); +?> + +
    +
    +
    + + 0) : ?> + +
    +
    + +
    +
    + + + + + + + + + + + + +
    +
    +
    +
    + +

    + + + + + +

    +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/list.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/list.php new file mode 100755 index 0000000..7782f7b --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/list.php @@ -0,0 +1,47 @@ +isEmpty()) : ?> + + + fetch()) : ?> +
    +

    +
    +
    + +
    +
    + + + %s
    ');?> + + + + + + + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/main.php new file mode 100755 index 0000000..08788e0 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/main.php @@ -0,0 +1,92 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + + + +
    +
    + + + + +
    +
    + + + + + + + + +
    +

    + +
    +
    + +
    +
    +
    +
    + + + + + + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/search_results.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/search_results.php new file mode 100755 index 0000000..7c7c239 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/calendar/search_results.php @@ -0,0 +1,44 @@ + + + +isEmpty()) : ?> + +

    + + fetch()) : ?> + +

    +
    + +
    + + + +
    + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/date-picker.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/date-picker.css new file mode 100755 index 0000000..d4677bc --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/date-picker.css @@ -0,0 +1,35 @@ +.date-picker { + border-collapse: collapse; + background: #fff; + color: #fff; + border: 1px solid #666; + border-width: 1px 2px 2px 1px; +} +.date-picker th { + border: none; + color: #000; + text-align: center; +} +.date-picker td { + border: 1px solid #666; + text-align: center; + padding: 4px 6px; +} +th.date-picker-month { + text-align: left; +} +th.date-picker-year { + text-align: right; +} +.date-picker-control, th.date-picker-control { + color: #060; + cursor: pointer; +} +.date-picker-day, .date-picker-today { + color: #000; + background: #eee; + cursor: pointer; +} +.date-picker-today { + background: #ccc; +} \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/cat.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/cat.php new file mode 100755 index 0000000..3efdcc8 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/cat.php @@ -0,0 +1,102 @@ + + +
    + + %s

    ') ?> + +

    +
    +
    + %s', '
  • %s
  • ', '
  • %s
  • ') ?> + + + isEmpty()) : ?> + +

    + +
      + fetch()) : ?> + +
    • + %s
    ', '
  • %s (%s)
  • ') ?> + + + +
    + + + + +
    +
    + +
    +

    '.tpl::lang('Websites',true).'

    +
    +
    ', + /* le bloc si il y a pas de lien */ '
    +
    +
    +
    +

    '.tpl::lang('Websites',true).'

    +
    +
    ', + /* un item : */ '

    %s

    %s', + /* nombre de liens � afficher : */ '2' , + /* option de tri */ 'random' + ) ?> + + isEmpty()) : ?> + + fetch()) : ?> + + + + +

    + +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/dir.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/dir.css new file mode 100755 index 0000000..15d815c --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/dir.css @@ -0,0 +1,96 @@ + +/* Lien cat�gorie parent +----------------------------------------------------------*/ +.parentCatLink { + text-align: right; + float: right; +} + +/* Titres +----------------------------------------------------------*/ +#puntal_content .directory span { + font-weight: bold; +} + +/* Menu +----------------------------------------------------------*/ +#dirMenu { + list-style-none: none; + padding-left: 0; +} +#dirMenu li { + display: inline; + margin-right: 1em; +} +#dirMenu li.active { + font-weight: bold; +} + +/* Totaux +----------------------------------------------------------*/ +.dirIntro { + text-align: center; +} + +/* Liste des cat�gories de l'annuaire +----------------------------------------------------------*/ +ul.cats { + margin: 0; + padding: 0; + list-style: none; +} +ul.cats li { + margin: 1.5em 0 1.5em 0; + padding: 0 1em 0 0; + float: left; + width: 48%; + height: auto; + line-height: 160%; + font-weight: bold; +} + +/* Liste des sous-cat�gories de l'annuaire +----------------------------------------------------------*/ +ul.cats ul.subcats { + margin: 0; + padding: 0; + list-style: none; + font-size: 0.9em; +} +ul.cats ul.subcats li { + background: none; + float: none; + display: block; + margin: 0; + padding: 0 0 0 20px; + line-height: 160%; + font-weight: normal; +} +ul.cats ul.subcats li a { + background: none; + padding-left: 0; + text-decoration: none; +} +ul.cats ul.subcats li a:hover { + text-decoration: underline; +} + +/* Les liens +----------------------------------------------------------*/ +.links { + padding-bottom: 2em; +} + +/* Google Page Ranks +----------------------------------------------------------*/ +.pageranks { padding-left: 45px; } +.pagerank1 { background: transparent url(img/pr1.png) no-repeat 0 50%; } +.pagerank2 { background: transparent url(img/pr2.png) no-repeat 0 50%; } +.pagerank3 { background: transparent url(img/pr3.png) no-repeat 0 50%; } +.pagerank4 { background: transparent url(img/pr4.png) no-repeat 0 50%; } +.pagerank5 { background: transparent url(img/pr5.png) no-repeat 0 50%; } +.pagerank6 { background: transparent url(img/pr6.png) no-repeat 0 50%; } +.pagerank7 { background: transparent url(img/pr7.png) no-repeat 0 50%; } +.pagerank8 { background: transparent url(img/pr8.png) no-repeat 0 50%; } +.pagerank9 { background: transparent url(img/pr9.png) no-repeat 0 50%; } +.pagerank10 { background: transparent url(img/pr10.png) no-repeat 0 50%; } diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/home.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/home.php new file mode 100755 index 0000000..421926a --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/home.php @@ -0,0 +1,76 @@ + + +
    +

    +
    +
    + %s', '
  • %s
  • ', '
  • %s
  • ') ?> +

    +

    + + +
    +
    + +
    +

    '.tpl::lang('Categories',true).'

    +
    +
    ', + /* le bloc si il y a pas de lien */ '
    +
    +
    +
    +

    '.tpl::lang('Categories',true).'

    +
    +
    ', + /* un item : */ '

    %s

    %s', + /* nombre de liens � afficher : */ '2' , + /* option de tri */ 'random' + ) ?> + + isEmpty()) : ?> +
      + fetch()) : ?> + +
    • + %s
    ', '
  • %s (%s)
  • ') ?> + + + +
    + +
    +
    +
    + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr10.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr10.png new file mode 100755 index 0000000..9399a1e Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr10.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr2.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr2.png new file mode 100755 index 0000000..42dcb6e Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr2.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr3.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr3.png new file mode 100755 index 0000000..78403ba Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr3.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr4.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr4.png new file mode 100755 index 0000000..fc49050 Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr4.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr5.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr5.png new file mode 100755 index 0000000..b914b91 Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr5.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr6.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr6.png new file mode 100755 index 0000000..174b1da Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr6.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr7.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr7.png new file mode 100755 index 0000000..e159117 Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr7.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr8.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr8.png new file mode 100755 index 0000000..39ffeee Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr8.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr9.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr9.png new file mode 100755 index 0000000..0d18f45 Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/img/pr9.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/list.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/list.php new file mode 100755 index 0000000..279ee9b --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/list.php @@ -0,0 +1,62 @@ + + +
    +

    +
    +
    + %s', '
  • %s
  • ', '
  • %s
  • ') ?> +

    +
    +
    +
    + +
    +

    +
    +
    + isEmpty()) : ?> + + fetch()) : ?> + + + + +

    + +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/main.php new file mode 100755 index 0000000..61a1f54 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/main.php @@ -0,0 +1,85 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + + +
    +
    + + + +
    +
    + + + + + + + + + + + + + + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/search.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/search.php new file mode 100755 index 0000000..59a88b5 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/search.php @@ -0,0 +1,49 @@ + + +

    Recherche sur l'annuaire

    + R�sultats de votre recherche de %s.

    '); ?> + + isEmpty()) : ?> + + fetch()) : ?> + +

    >

    + +
      +
    • +
    • Adresse :
    • +
    • Rubrique :
    • + Google PageRank : %s'); ?> +
    + + + +

    Aucun r�sultats.

    + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/search_results.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/search_results.php new file mode 100755 index 0000000..da69418 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/search_results.php @@ -0,0 +1,45 @@ + + + +isEmpty()) : ?> + +

    + + fetch()) : ?> + + +
    + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/submit.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/submit.php new file mode 100755 index 0000000..6240ba6 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/directory/submit.php @@ -0,0 +1,85 @@ + + +
    +

    +
    +
    + %s', '
  • %s
  • ', '
  • %s
  • ') ?> +

    +
    +
    +
    + + + +
    +

    +
    +
    + +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + +

    +

    + +

    +

    + +

    +

    + +

    +

    +generatePtb('l_desc'); +?> +

    +

    + +
    +
    +
    +

    + +

    +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/cat.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/cat.php new file mode 100755 index 0000000..27e3727 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/cat.php @@ -0,0 +1,27 @@ + + + + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/dl_rss.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/dl_rss.php new file mode 100755 index 0000000..e349327 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/dl_rss.php @@ -0,0 +1,36 @@ + + + + <?php tpl::headTitlePage() ?> + + + + Puntal 2 + + + + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/downloads.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/downloads.css new file mode 100755 index 0000000..15534d0 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/downloads.css @@ -0,0 +1,114 @@ +/* Titres +----------------------------------------------------------*/ +#puntal_content #downloadsTitle span { + font-weight: bold; +} + +/* Cat�gories */ + +#catBox ul { +} + +#catBox li { + display: inline; + margin-right: 12px; +} +#catBox li.current { + font-weight: bold; +} + +/* Contenu des downloads +----------------------------------------------------------*/ +#downloadContent { +} + +#downloadContent p { + margin: 1em 0; +} + +#downloadContent pre { + background-color: #333; + color: #fff; + padding: 5px; + border: 1px solid #ccc; +} + +#downloadContent h3, #downloadContent h4, #downloadContent h5, #downloadContent h6 { + display: block; + font-weight: bold; +} + +#downloadContent h3 { + font-size: 1.4em; + margin: 1em 0; +} +#downloadContent h4 { + font-size: 1.3em; + margin: 1.33em 0; +} +#downloadContent h5 { + font-size: 1.2em; + margin: 1.67em 0; +} +#downloadContent h6 { + font-size: 1.1em; + margin: 2.33em 0; +} + + +/* Lists settings from Mozilla Firefox */ +#downloadContent ul, +#downloadContent ol { + display: block; + margin: 1em 0; + padding-left: 40px; +} + +#downloadContent ul, +#downloadContent ul li { + list-style-type: disc; +} + +#downloadContent ol, +#downloadContent ol li { + list-style-type: decimal; +} + +#downloadContent li { + display: list-item; +} + + +/* nested lists have no top/bottom margins */ +#downloadContent ul ul, +#downloadContent ul ol, +#downloadContent ul dl, +#downloadContent ol ul, +#downloadContent ol ol, +#downloadContent ol dl, +#downloadContent dl ul, +#downloadContent dl ol, +#downloadContent dl dl { + margin-top: 0; + margin-bottom: 0; +} + +/* 2 deep unordered lists use a circle */ +#downloadContent ol ul, +#downloadContent ul ul, +#downloadContent ol ul li, +#downloadContent ul ul li { + list-style-type: circle; +} + +/* 3 deep (or more) unordered lists use a square */ +#downloadContent ol ol ul, +#downloadContent ol ul ul, +#downloadContent ul ol ul, +#downloadContent ul ul ul, +#downloadContent ol ol ul li, +#downloadContent ol ul ul li, +#downloadContent ul ol ul li, +#downloadContent ul ul ul li { + list-style-type: square; +} diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/file.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/file.php new file mode 100755 index 0000000..88b57b2 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/file.php @@ -0,0 +1,39 @@ + + + +
    +
    +
    + +
    +
    +
    +
    + + +
    +

    +
    +
    + + + %s

    ', # le lien "normal" + '

    %s

    ' # si pas autoris� + ) ?> + + +

    + + - - %s') ?> +

    +
    +
    +
    +
    + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/home.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/home.php new file mode 100755 index 0000000..dfae838 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/home.php @@ -0,0 +1,80 @@ + + + + +
    +

    +
    + + + + + + + + + + fetch()) : ?> + + + + + + + +

    +
    +
    + + + + + +
    +

    +
    + + + + + + + + + fetch()) : ?> + + + + + + +

    +
    +
    + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/main.php new file mode 100755 index 0000000..59c6bba --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/main.php @@ -0,0 +1,104 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + + + + +
    +
    + + + +
    +
    + +
    +

    +
    +
    + + %s', # format du bloc + '
  • %s
  • ', # format d'un �l�ment + '
  • %s
  • ', # format d'un �l�ment actif + ' (%s)' # format du nombre de t�l�chargement dans une cat�gorie + ) ?> + + + + %s', '
  • %s
  • ') ?> + +
    +
    +
    + + + + + + + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/search_results.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/search_results.php new file mode 100755 index 0000000..d7de7c7 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/downloads/search_results.php @@ -0,0 +1,42 @@ + + + +isEmpty()) : ?> + +

    + + fetch()) : ?> + +

    +
    + + + +
    + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/error.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/error.php new file mode 100755 index 0000000..a1c2602 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/error.php @@ -0,0 +1,65 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + +
    +
    + + + +
    +
    + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/logo-forums.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/logo-forums.png new file mode 100644 index 0000000..789553c Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/logo-forums.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/minus.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/minus.png new file mode 100755 index 0000000..b58fcc3 Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/minus.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/plus.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/plus.png new file mode 100755 index 0000000..8eebc1c Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/img/plus.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/index.php new file mode 100755 index 0000000..48d200f --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/form_add.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/form_add.php new file mode 100755 index 0000000..9572ccb --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/form_add.php @@ -0,0 +1,73 @@ + + + + +
    +

    +
    +
    + %s', '
  • %s
  • ') ?> +
    +
    +
    + + +
    +

    +
    +
    +
    +
    +
    +

    +

    +

    +

    +generatePtb('p_def'); +?> +

    +

    +generatePtb('p_ex'); +?> +
    +
    +
    +

    + + + +

    +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/form_edit.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/form_edit.php new file mode 100755 index 0000000..1f3deb5 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/form_edit.php @@ -0,0 +1,74 @@ + + + + +
    +

    +
    +
    + %s', '
  • %s
  • ') ?> +
    +
    +
    + + +
    +

    +
    +
    +
    +
    +
    + +

    +

    + +

    +

    +generatePtb('p_def'); +?> +

    +

    +generatePtb('p_ex'); +?> +
    +
    +
    +

    + + +

    +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/lexicon.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/lexicon.css new file mode 100755 index 0000000..0462e65 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/lexicon.css @@ -0,0 +1,39 @@ +#puntal_content .lexiconTitle span { + font-weight: bold; +} +#puntal_content .word { + border: none; + margin: 0; + font-weight: normal; +} + +p#letter_list { text-align: center; } + +p#letter_list a.cur_letter { font-weight: bold; } + +div.lexique dt.word { + margin-top: 1em; + font-weight: bold; +} + +div.lexique dd.word_def {} + +div.lexique dd.word_example { + font-style: italic; +} +div.lexique dd.word_example em { + font-style: normal; +} + +div.lexique dd.word_infos { + text-align: right; +} + +div.lexique dt.inactive, +div.lexique dd.inactive { + color: #696969; +} + +#puntal_content #lexiqueTitle span { + font-weight: bold; +} diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_all.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_all.php new file mode 100755 index 0000000..c5e8530 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_all.php @@ -0,0 +1,45 @@ + + +
    +
    + +
    +
    + +
    +

    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_letter.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_letter.php new file mode 100755 index 0000000..e8e7e02 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_letter.php @@ -0,0 +1,59 @@ + + +
    +

    -

    +
    +
    + isEmpty()) : ?> +
    + fetch()) : ?> +
    +
    +
    +
    + %s') ?> + + +
    + + + + %s') ?> + + %s') ?> + %s') ?> + +
    + + + +
    + +

    + +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_validate.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_validate.php new file mode 100755 index 0000000..4516958 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/list_validate.php @@ -0,0 +1,52 @@ + + +
    +

    +
    +
    + isEmpty()) : ?> +
    + fetch()) : ?> +
    +
    + %s') ?> +
    + + + + %s') ?> + %s') ?> + %s') ?> +
    + +
    + +

    + +
    +
    +
    + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/main.php new file mode 100755 index 0000000..b2cc0e7 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/main.php @@ -0,0 +1,123 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + + +
    +
    + + + +
    +
    + +
    +

    +
    +

    + %s') ?> + + %s') ?> + + + %s', + ' - %s' ) ?> + + %s', + ' - %s' ) ?> + + %s', + ' - %s' ) ?> +

    +

    + %s', + '%s', + ' - ') ?> +

    +
    +
    + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/search_results.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/search_results.php new file mode 100755 index 0000000..290721c --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/lexicon/search_results.php @@ -0,0 +1,42 @@ + + + +isEmpty()) : ?> + +

    + + fetch()) : ?> + +

    +
    + + + +
    + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/main.php new file mode 100755 index 0000000..5503511 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/main.php @@ -0,0 +1,120 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + + + +
    +
    + + + +
    +
    + + + + %s

    ') ?> + + + fetch()) : ?> +
    + +

    +
    +
    + + +

    + +
    + + |

    +
    +
    +
    + + + +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + +*/ +?> + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/news.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/news.css new file mode 100755 index 0000000..a99078a --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/news.css @@ -0,0 +1,46 @@ + +/* News title +----------------------------------------------------------*/ + +h2.news a:link { text-decoration: none; color: #EEE } +h2.news a:hover { text-decoration: underline; color: #FFF; } +h2.news a:active { text-decoration: underline; color: #FFF; } +h2.news a:visited { text-decoration: none; color: #EEE; } +h2.news a:visited:hover { text-decoration: underline; color: #FFF; } + +/* Infos news +----------------------------------------------------------*/ +p.infos { + text-align: right; +} + +/* Avatar +----------------------------------------------------------*/ +.newsAvatar { + float: left; + text-align: left; + clear: left; + width: auto; + padding: 1em; + margin: 0; +} + +.newsAvatar img { + padding-right: 1em; + padding-bottom: 1em; +} + +/* RSS buton +----------------------------------------------------------*/ +a.rss:link, a.rss:visited, a.rss:active { + color: #fff; + background: #f90; + border: 1px outset #f90; + text-decoration: none; + padding: 0.1em 0.3em; + font-size: 85%; +} +a.rss:hover { + color: #fff; + border: 1px inset #f90; +} diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/news_static.tpl b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/news_static.tpl new file mode 100755 index 0000000..e62d051 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/news_static.tpl @@ -0,0 +1,12 @@ + +
    +

    +
    +
    + + + +

    |

    +
    +
    +
    diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/rss.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/rss.php new file mode 100755 index 0000000..884b70d --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/news/rss.php @@ -0,0 +1,43 @@ + + + + <?php tpl::headTitlePage() ?> - <?php tpl::lang('News') ?> + + ]]> + + Puntal 2 + + fetch()) : ?> + + <?php news::title() ?> + + ]]> + + + + + + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/main.php new file mode 100755 index 0000000..9bee0b4 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/main.php @@ -0,0 +1,86 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + + +
    +
    + + + +
    +
    + fetch()) : ?> +
    +

    +

    + +
    + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/planet.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/planet.css new file mode 100755 index 0000000..2ed536f --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/planet/planet.css @@ -0,0 +1,27 @@ +/* Titres */ +#puntal_content .feedTitle { + margin-bottom: 0; +} + +/* Infos */ +#puntal_content .feedInfos { + margin-top: 0; + font-size: 0.9em; +} + +/* Liens */ +#puntal_content .feedLinks { + font-size: 0.9em; + list-style-type: none; + padding-left: 0; + text-align: right; +} +#puntal_content .feedLinks li { + display: inline; + margin-right: 1em; +} +#puntal_content .feedLinks li a { + padding: 0.2em 0 0.2em 18px; +} + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/redirect.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/redirect.php new file mode 100755 index 0000000..8585c82 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/redirect.php @@ -0,0 +1,56 @@ + + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + +
    +
    + +
    +

    +
    +
    +

    +

    +
    +
    +
    + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/search/main.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/search/main.php new file mode 100755 index 0000000..620d840 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/search/main.php @@ -0,0 +1,88 @@ + + + + + + <?php tpl::headTitlePage() ?> + + + + + + + + +
    +
    + + + +
    +
    + +
    +

    +
    +
    + + +
    +
    +
    + +
    +

    +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    + + +
    +
    + + + +
    +
    + + + diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/style.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/style.css new file mode 100755 index 0000000..4d28aac --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/style.css @@ -0,0 +1,70 @@ +/* En-tete / header */ +div#brdtitle { position: relative; } + +p#prelude { + position: absolute; + top: 0.3em; + right: 0.3em; + font-size: 0.8em; +} + +p#prelude a:link { text-decoration: none; color: #EEE } +p#prelude a:hover { text-decoration: underline; color: #FFF; } +p#prelude a:active { text-decoration: underline; color: #FFF; } +p#prelude a:visited { text-decoration: none; color: #EEE; } +p#prelude a:visited:hover { text-decoration: underline; color: #FFF; } + +/* Colone � gauche / Column on left */ +div#puntal_main { + width: 100%; + float: right; + margin-left: -22em; +} +div#puntal_content { + margin-left: 22em; + padding-left: 1ex; +} +div#puntal_sidebar { + width: 21em; + float: left; +} + +/* Styles for collapse icon and text */ +div.block h2 img.icon { + vertical-align: text-bottom; +} +div.block h2 a { + text-decoration: none; +} + +/* Listes colone lat�rale / Lateral column list */ +div#puntal_sidebar li { + padding-top: 0.2em; + padding-bottom: 0.2em; +} +div#puntal_sidebar li.active { + font-weight: bold; +} + + +/* Sous-menu t�l�chargements / Downloads sub-menu */ +#sousmenu ul, #sousmenu li { + list-style-type: none; + display: inline; +} +#sousmenu li { margin-right: 12px; } +#sousmenu a:link, #sousmenu a:visited{ text-decoration: none; } +#sousmenu a:hover { text-decoration: underline; } + +/* Boite bloc �dito / Edito block box */ +#box_edito div.box { border-width: 1px; } +#box_edito div.box p { + line-height: 150%; + letter-spacing: 0.08em; +} + +/* Boite bloc RSS reader / RSS reader block box */ +#box_rssreader h3 { + margin-top: 1em; + font-weight: bold; +} diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/img/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/img/index.php new file mode 100755 index 0000000..7931413 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/img/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/img/supprimer.png b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/img/supprimer.png new file mode 100755 index 0000000..b6ed32d Binary files /dev/null and b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/img/supprimer.png differ diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/index.php b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/index.php new file mode 100755 index 0000000..12dd1e9 --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/index.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/tribune.css b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/tribune.css new file mode 100755 index 0000000..0d9174e --- /dev/null +++ b/Identity/Webenv/Themes/Default/Puntal/themes/punbb/tribune/tribune.css @@ -0,0 +1,4 @@ +.wall { +height:100px; +overflow:auto; +} \ No newline at end of file