diff options
| author | hmeland | 1999-06-04 14:26:38 +0000 |
|---|---|---|
| committer | hmeland | 1999-06-04 14:26:38 +0000 |
| commit | ab174f1e9be31f39b5777b5f6ec127dc44d22310 (patch) | |
| tree | a9e3dba146ea046fc6e60c7284cbdd93bb600555 /Mailman/MailList.py | |
| parent | 562feff6072b0429b3692c7bb8b884c1ea0b11ec (diff) | |
| download | mailman-ab174f1e9be31f39b5777b5f6ec127dc44d22310.tar.gz mailman-ab174f1e9be31f39b5777b5f6ec127dc44d22310.tar.zst mailman-ab174f1e9be31f39b5777b5f6ec127dc44d22310.zip | |
Changes to speed up mass subscription via the web:
MailList.py:
Changed MailList.SetUserOption() to take a `save_list' keyword
argument (defaulting to true). When false, SetUserOption won't do
self.Save() after changing the option.
New function MailList.ApprovedAddMembers() (note plural) that takes
a list of prospective new list members (and possibly a list of
corresponding passwords), and does _all_ the necessary list changes
before saving the list configuration. Empty passwords are
substituted with randomly generated ones. Returns a dict with
{address: exception_tuple} entries -- exception_tuple is either None
or a two-element tuple containing the first exception type and value
raised when trying to add address. The exception traceback object
isn't included in the returned dict, because a) I don't think it is
very useful for the relevant exceptions, and b) using it wrongly
could cause some fuzz with Python's garbage collector -- i.e. we
would leak memory.
Changed MailList.ApprovedAddMember() to be a mere wrapper, calling
the new ApprovedAddMembers() function and reraising any exception in
the returned dict.
Also made the logic of the code doing subject prefixing a bit
clearer, and changed MailList.aside_new() so that list's config.db
files are saved with umask 007 (as they contain all list members'
passwords in clear text).
Utils.py:
New function MakeRandomPassword(length=4), used by
MailList.ApprovedAddMembers() whenever empty passwords are found.
The default random password length should possibly be made site
configurable.
Also, fixed an error in the _badchars regular expression -- the
final "," was probably meant to be inside the character set.
Cgi/admin.py:
Changed ChangeOptions() to use the new MailList.ApprovedAddMembers()
function.
Diffstat (limited to 'Mailman/MailList.py')
| -rw-r--r-- | Mailman/MailList.py | 145 |
1 files changed, 106 insertions, 39 deletions
diff --git a/Mailman/MailList.py b/Mailman/MailList.py index d43025283..250740a20 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -199,7 +199,7 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, return 0 return not not self.user_options[user] & option - def SetUserOption(self, user, option, value): + def SetUserOption(self, user, option, value, save_list=1): if not self.user_options.has_key(user): self.user_options[user] = 0 if value: @@ -208,7 +208,8 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, self.user_options[user] = self.user_options[user] & ~(option) if not self.user_options[user]: del self.user_options[user] - self.Save() + if save_list: + self.Save() # Here are the rules for the three dictionaries self.members, # self.digest_members, and self.passwords: @@ -887,11 +888,36 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, else: # approval needed self.AddRequest('add_member', digest, name, password) raise Errors.MMNeedApproval, self.GetAdminEmail() - - def ApprovedAddMember(self, name, password, digest, ack=None, admin_notif=None): + res = self.ApprovedAddMembers([name], [password], + digest, ack, admin_notif) + # There should be exactly one (key, value) pair in the returned dict, + # extract the possible exception value + res = res.values()[0] + if res is None: + # User was added successfully + return + else: + # Split up the exception list and reraise it here + e, v = res + raise e, v + + def ApprovedAddMembers(self, names, passwords, digest, + ack=None, admin_notif=None): + """Subscribe members in list `names'. + + Passwords can be supplied in the passwords list. If an empty + password is encountered, a random one is generated and used. + + Returns a dict where the keys are addresses that were tried + subscribed, and the corresponding values are either two-element + tuple containing the first exception type and value that was + raised when trying to add that address, or `None' to indicate + that no exception was raised. + + """ if ack is None: if self.send_welcome_msg: ack = 1 @@ -902,38 +928,77 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, admin_notif = 1 else: admin_notif = 0 - # normalize the name, it could be of the form - # - # <person@place.com> User Name - # person@place.com (User Name) - # etc - # - name = Utils.ParseAddrs(name) - Utils.ValidateEmail(name) - name = Utils.LCDomain(name) - if self.IsMember(name): - raise Errors.MMAlreadyAMember - self.__AddMember(name, digest) - if digest: - kind = " (D)" - else: - kind = "" - self.SetUserOption(name, mm_cfg.DisableMime, - 1 - self.mime_is_default_digest) - self.LogMsg("subscribe", "%s: new%s %s", - self._internal_name, kind, name) - self.passwords[string.lower(name)] = password - if ack: - self.SendSubscribeAck(name, password, digest) - if admin_notif: - import Message - txt = Utils.maketext("adminsubscribeack.txt", - {"mmowner": mm_cfg.MAILMAN_OWNER, - "admin_email": self.GetAdminEmail(), - "listname": self.real_name, - "member": name}, 1) - msg = Message.IncomingMessage(txt) - self.DeliverToOwner(msg, self.owner) + if type(passwords) is type([]): + pass + else: + # Type error -- ignore the original value + passwords = [None] * len(names) + while len(passwords) < len(names): + passwords.append(None) + result = {} + dirty = 0 + for i in range(len(names)): + try: + # normalize the name, it could be of the form + # + # <person@place.com> User Name + # person@place.com (User Name) + # etc + # + name = Utils.ParseAddrs(names[i]) + Utils.ValidateEmail(name) + name = Utils.LCDomain(name) + except (Errors.MMBadEmailError, Errors.MMHostileAddress): + # We don't really need the traceback object for the exception, + # and as using it in the wrong way prevents garbage collection + # from working smoothly, we strip it away + result[name] = sys.exc_info()[:2] + # WIBNI we could `continue' within `try' constructs... + if result.has_key(name): + continue + if self.IsMember(name): + result[name] = [Errors.MMAlreadyAMember, None] + continue + self.__AddMember(name, digest) + self.SetUserOption(name, mm_cfg.DisableMime, + 1 - self.mime_is_default_digest, + save_list=0) + # Make sure we set a "good" password + password = passwords[i] + if not password: + password = Utils.MakeRandomPassword() + self.passwords[string.lower(name)] = password + # An address has been added successfully, make sure the + # list config is saved later on + dirty = 1 + result[name] = None + + if dirty: + self.Save() + if digest: + kind = " (D)" + else: + kind = "" + for name in result.keys(): + if result[name] is None: + self.LogMsg("subscribe", "%s: new%s %s", + self._internal_name, kind, name) + if ack: + self.SendSubscribeAck( + name, + self.passwords[string.lower(name)], + digest) + if admin_notif: + import Message + txt = Utils.maketext( + "adminsubscribeack.txt", + {"mmowner": mm_cfg.MAILMAN_OWNER, + "admin_email": self.GetAdminEmail(), + "listname": self.real_name, + "member": name}, 1) + msg = Message.IncomingMessage(txt) + self.DeliverToOwner(msg, self.owner) + return result def ProcessConfirmation(self, cookie): from Pending import Pending @@ -1204,8 +1269,8 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, prefix = self.subject_prefix if not subj: msg.SetHeader('Subject', '%s(no subject)' % prefix) - elif not re.match("(re:? *)?" + re.escape(self.subject_prefix), - subj, re.I): + elif prefix and not re.match(re.escape(self.subject_prefix), + subj, re.I): msg.SetHeader('Subject', '%s%s' % (prefix, subj)) if self.anonymous_list: del msg['reply-to'] @@ -1291,7 +1356,9 @@ def aside_new(old_name, new_name, reopen=0): """Utility to move aside a file, optionally returning a fresh open version. We ensure maintanance of proper umask in the process.""" - ou = os.umask(002) + # Make config.db unreadable by `other', as it contains all the list + # members' passwords (in clear text). + ou = os.umask(007) try: if os.path.exists(new_name): os.unlink(new_name) |
