"""Routines which rectify an old mailing list with current structure.
The MailList.CheckVersion() method looks for an old .data_version setting in
the loaded structure, and if found calls the Update() routine from this
module, supplying the list and the state last loaded from storage. The state
is necessary to distinguish from default assignments done in the .InitVars()
methods, before .CheckVersion() is called.
For new versions you should add sections to the UpdateOldVars() and the
UpdateOldUsers() sections, to preserve the sense of settings across structural
changes. Note that the routines have only one pass - when .CheckVersions()
finds a version change it runs this routine and then updates the data_version
number of the list, and then does a .Save(), so the transformations won't be
run again until another version change is detected.
"""
from types import ListType, StringType
from Mailman import mm_cfg
from Mailman import Utils
from Mailman import Message
from Mailman.MemberAdaptor import UNKNOWN
from Mailman.Logging.Syslog import syslog
def Update(l, stored_state):
"Dispose of old vars and user options, mapping to new ones when suitable."
ZapOldVars(l)
UpdateOldUsers(l)
NewVars(l)
UpdateOldVars(l, stored_state)
CanonicalizeUserOptions(l)
NewRequestsDatabase(l)
def ZapOldVars(mlist):
for name in ('num_spawns', 'filter_prog', 'clobber_date',
'public_archive_file_dir', 'private_archive_file_dir',
'archive_directory',
'minimum_removal_date',
'minimum_post_count_before_bounce_action',
'automatic_bounce_action',
'max_posts_between_bounces',
):
if hasattr(mlist, name):
delattr(mlist, name)
uniqueval = []
def UpdateOldVars(l, stored_state):
"""Transform old variable values into new ones, deleting old ones.
stored_state is last snapshot from file, as opposed to from InitVars()."""
def PreferStored(oldname, newname, newdefault=uniqueval,
l=l, state=stored_state):
"""Use specified old value if new value is not in stored state.
If the old attr does not exist, and no newdefault is specified, the
new attr is *not* created - so either specify a default or be positive
that the old attr exists - or don't depend on the new attr.
"""
if hasattr(l, oldname):
if not state.has_key(newname):
setattr(l, newname, getattr(l, oldname))
delattr(l, oldname)
if not hasattr(l, newname) and newdefault is not uniqueval:
setattr(l, newname, newdefault)
if hasattr(l, 'dont_respond_to_post_requests'):
oldval = getattr(l, 'dont_respond_to_post_requests')
if not hasattr(l, 'respond_to_post_requests'):
l.respond_to_post_requests = not oldval
del l.dont_respond_to_post_requests
if not hasattr(l, 'default_member_moderation'):
l.default_member_moderation = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION
if not hasattr(l, 'accept_these_nonmembers'):
l.accept_these_nonmembers = []
if not hasattr(l, 'hold_these_nonmembers'):
l.hold_these_nonmembers = []
if not hasattr(l, 'reject_these_nonmembers'):
l.reject_these_nonmembers = []
if not hasattr(l, 'discard_these_nonmembers'):
l.discard_these_nonmembers = []
if not hasattr(l, 'forward_auto_discards'):
l.forward_auto_discards = mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS
if not hasattr(l, 'generic_nonmember_action'):
l.generic_nonmember_action = mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION
if hasattr(l, 'moderated'):
if l.moderated:
for addr in l.posters:
if not l.isMember(addr):
l.accept_these_nonmembers.append(addr)
for member in l.getMembers():
l.setMemberOption(member, mm_cfg.Moderate,
member not in l.posters)
l.generic_nonmember_action = 1
l.default_member_moderation = 1
elif l.member_posting_only:
for addr in l.posters:
if not l.isMember(addr):
l.accept_these_nonmembers.append(addr)
for member in l.getMembers():
l.setMemberOption(member, mm_cfg.Moderate, 0)
l.generic_nonmember_action = 1
l.default_member_moderation = 0
elif not l.posters:
for member in l.getMembers():
l.setMemberOption(member, mm_cfg.Moderate, 0)
l.generic_nonmember_action = 0
l.default_member_moderation = 0
else:
for addr in l.posters:
if not l.isMember(addr):
l.accept_these_nonmembers.append(addr)
for member in l.getMembers():
l.setMemberOption(member, mm_cfg.Moderate,
member not in l.posters)
l.generic_nonmember_action = 1
l.default_member_moderation = 1
del l.moderated
del l.posters
del l.member_posting_only
if hasattr(l, 'forbidden_posters'):
forbiddens = l.forbidden_posters
for addr in forbiddens:
if l.isMember(addr):
l.setMemberOption(addr, mm_cfg.Moderate, 1)
else:
l.hold_these_nonmembers.append(addr)
del l.forbidden_posters
PreferStored('reminders_to_admins', 'umbrella_list',
mm_cfg.DEFAULT_UMBRELLA_LIST)
PreferStored('auto_subscribe', 'open_subscribe')
PreferStored('closed', 'private_roster')
PreferStored('mimimum_post_count_before_removal',
'mimimum_post_count_before_bounce_action')
PreferStored('bad_posters', 'forbidden_posters')
PreferStored('automatically_remove', 'automatic_bounce_action')
if hasattr(l, "open_subscribe"):
if l.open_subscribe:
if mm_cfg.ALLOW_OPEN_SUBSCRIBE:
l.subscribe_policy = 0
else:
l.subscribe_policy = 1
else:
l.subscribe_policy = 2 delattr(l, "open_subscribe")
if not hasattr(l, "administrivia"):
setattr(l, "administrivia", mm_cfg.DEFAULT_ADMINISTRIVIA)
if not hasattr(l, "admin_member_chunksize"):
setattr(l, "admin_member_chunksize",
mm_cfg.DEFAULT_ADMIN_MEMBER_CHUNKSIZE)
if hasattr(l, "posters_includes_members"):
if l.posters_includes_members:
if l.posters:
l.member_posting_only = 1
else:
if l.posters:
l.member_posting_only = 0
delattr(l, "posters_includes_members")
elif l.data_version <= 10 and l.posters:
l.member_posting_only = 0
if type(l.members) is ListType:
members = {}
for m in l.members:
members[m] = 1
l.members = members
if type(l.digest_members) is ListType:
dmembers = {}
for dm in l.digest_members:
dmembers[dm] = 1
l.digest_members = dmembers
if not hasattr(l, "admin_notify_mchanges"):
setattr(l, "admin_notify_mchanges",
mm_cfg.DEFAULT_ADMIN_NOTIFY_MCHANGES)
for k in l.members.keys():
if k.lower() <> k:
l.members[k.lower()] = Utils.LCDomain(k)
del l.members[k]
elif type(l.members[k]) == StringType and k == l.members[k].lower():
pass
else:
l.members[k] = 0
for k in l.digest_members.keys():
if k.lower() <> k:
l.digest_members[k.lower()] = Utils.LCDomain(k)
del l.digest_members[k]
elif type(l.digest_members[k]) == StringType and \
k == l.digest_members[k].lower():
pass
else:
l.digest_members[k] = 0
def NewVars(l):
"""Add defaults for these new variables if they don't exist."""
def add_only_if_missing(attr, initval, l=l):
if not hasattr(l, attr):
setattr(l, attr, initval)
add_only_if_missing('autorespond_postings', 0)
add_only_if_missing('autorespond_admin', 0)
add_only_if_missing('autorespond_requests', 0)
add_only_if_missing('autoresponse_postings_text', '')
add_only_if_missing('autoresponse_admin_text', '')
add_only_if_missing('autoresponse_request_text', '')
add_only_if_missing('autoresponse_graceperiod', 90)
add_only_if_missing('postings_responses', {})
add_only_if_missing('admin_responses', {})
add_only_if_missing('reply_goes_to_list', '')
add_only_if_missing('preferred_language', mm_cfg.DEFAULT_SERVER_LANGUAGE)
add_only_if_missing('available_languages', [])
add_only_if_missing('digest_volume_frequency',
mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY)
add_only_if_missing('digest_last_sent_at', 0)
add_only_if_missing('mod_password', None)
add_only_if_missing('moderator', [])
add_only_if_missing('topics', [])
add_only_if_missing('topics_enabled', 0)
add_only_if_missing('topics_bodylines_limit', 5)
add_only_if_missing('one_last_digest', {})
add_only_if_missing('usernames', {})
add_only_if_missing('personalize', 0)
add_only_if_missing('first_strip_reply_to',
mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO)
add_only_if_missing('unsubscribe_policy',
mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY)
add_only_if_missing('send_goodbye_msg', mm_cfg.DEFAULT_SEND_GOODBYE_MSG)
add_only_if_missing('include_rfc2369_headers', 1)
add_only_if_missing('include_list_post_header', 1)
add_only_if_missing('bounce_score_threshold',
mm_cfg.DEFAULT_BOUNCE_SCORE_THRESHOLD)
add_only_if_missing('bounce_info_stale_after',
mm_cfg.DEFAULT_BOUNCE_INFO_STALE_AFTER)
add_only_if_missing('bounce_you_are_disabled_warnings',
mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS)
add_only_if_missing(
'bounce_you_are_disabled_warnings_interval',
mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL)
add_only_if_missing(
'bounce_unrecognized_goes_to_list_owner',
mm_cfg.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER)
add_only_if_missing(
'bounce_notify_owner_on_disable',
mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE)
add_only_if_missing(
'bounce_notify_owner_on_removal',
mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL)
add_only_if_missing('ban_list', [])
add_only_if_missing('filter_mime_types', mm_cfg.DEFAULT_FILTER_MIME_TYPES)
add_only_if_missing('pass_mime_types', mm_cfg.DEFAULT_PASS_MIME_TYPES)
add_only_if_missing('filter_content', mm_cfg.DEFAULT_FILTER_CONTENT)
add_only_if_missing('convert_html_to_plaintext',
mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT)
add_only_if_missing('filter_action', mm_cfg.DEFAULT_FILTER_ACTION)
add_only_if_missing('delivery_status', {})
add_only_if_missing('member_moderation_action', 0)
add_only_if_missing('member_moderation_notice', '')
add_only_if_missing('new_member_options',
mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS)
add_only_if_missing('emergency', 0)
add_only_if_missing('hold_and_cmd_autoresponses', {})
add_only_if_missing('news_prefix_subject_too', 1)
if Utils.GetCharSet(l.preferred_language) == 'us-ascii':
encode = 0
else:
encode = 2
add_only_if_missing('encode_ascii_prefixes', encode)
add_only_if_missing('news_moderation', 0)
def UpdateOldUsers(mlist):
"""Transform sense of changed user options."""
passwords = {}
for k, v in mlist.passwords.items():
passwords[k.lower()] = v
mlist.passwords = passwords
from Mailman.Bouncer import _BounceInfo
for m in mlist.bounce_info.keys():
if not mlist.isMember(m) or not isinstance(mlist.getBounceInfo(m),
_BounceInfo):
del mlist.bounce_info[m]
def CanonicalizeUserOptions(l):
"""Fix up the user options."""
if getattr(l, 'useropts_version', 0) > 0:
return
options = {}
for k, v in l.user_options.items():
if k is None:
continue
lcuser = k.lower()
flags = 0
if options.has_key(lcuser):
flags = options[lcuser]
flags |= v
options[lcuser] = flags
l.user_options = options
for k, v in l.user_options.items():
if not l.isMember(k):
del l.user_options[k]
continue
if l.getMemberOption(k, mm_cfg.DisableDelivery):
l.setDeliveryStatus(k, UNKNOWN)
l.setMemberOption(k, mm_cfg.DisableDelivery, 0)
l.useropts_version = 1
def NewRequestsDatabase(l):
"""With version 1.2, we use a new pending request database schema."""
r = getattr(l, 'requests', {})
if not r:
return
for k, v in r.items():
if k == 'post':
for p in v:
author, text = p[2]
reason = p[3]
msg = Message.OutgoingMessage(text)
l.HoldMessage(msg, reason)
del r[k]
elif k == 'add_member':
for ign, ign, digest, addr, password in v:
l.HoldSubscription(addr, password, digest)
del r[k]
else:
syslog('error', """\
VERY BAD NEWS. Unknown pending request type `%s' found for list: %s""",
k, l.internal_name())