diff options
| -rw-r--r-- | Mailman/Cgi/admin.py | 55 | ||||
| -rw-r--r-- | Mailman/MailList.py | 145 | ||||
| -rw-r--r-- | Mailman/Utils.py | 10 |
3 files changed, 143 insertions, 67 deletions
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 19256a92f..9e875d1f0 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -793,40 +793,41 @@ def ChangeOptions(lst, category, cgi_info, document): if cgi_info.has_key('subscribees'): name_text = cgi_info['subscribees'].value name_text = string.replace(name_text, '\r', '') - names = string.split(name_text, '\n') - subscribe_success = [] - subscribe_errors = [] + names = map(string.strip, string.split(name_text, '\n')) send_welcome_msg = string.atoi( cgi_info["send_welcome_msg_to_this_batch"].value) - for new_name in map(string.strip, names): - new_name = Utils.ParseAddrs(new_name) + digest = 0 + if not lst.digestable: digest = 0 - if not lst.digestable: - digest = 0 - if not lst.nondigestable: - digest = 1 - try: - Utils.ValidateEmail(new_name) - lst.ApprovedAddMember( - new_name, - Utils.GetRandomSeed() + Utils.GetRandomSeed(), - digest, send_welcome_msg) - subscribe_success.append(new_name) - except Errors.MMAlreadyAMember: - subscribe_errors.append((new_name, 'Already a member')) - except Errors.MMBadEmailError: - if new_name == '': - new_name = '<blank line>' - subscribe_errors.append( - (new_name, "Bad/Invalid email address")) - except Errors.MMHostileAddress: - subscribe_errors.append( - (new_name, "Hostile Address (illegal characters)")) + if not lst.nondigestable: + digest = 1 + subscribe_errors = [] + subscribe_success = [] + result = lst.ApprovedAddMembers(names, None, + digest, send_welcome_msg) + for name in result.keys(): + if result[name] is None: + subscribe_success.append(name) + else: + # `name' was not subscribed, find out why. On failures, + # result[name] is set from sys.exc_info()[:2] + e, v = result[name] + if e is Errors.MMAlreadyAMember: + subscribe_errors.append((name, 'Already a member')) + elif e is Errors.MMBadEmailError: + if name == '': + name = '<blank line>' + subscribe_errors.append( + (name, "Bad/Invalid email address")) + elif e is Errors.MMHostileAddress: + subscribe_errors.append( + (name, "Hostile Address (illegal characters)")) if subscribe_success: document.AddItem(Header(5, "Successfully Subscribed:")) document.AddItem(apply(UnorderedList, tuple((subscribe_success)))) document.AddItem("<p>") - dirty = 1 + # ApprovedAddMembers will already have saved the list for us. + # dirty = 1 if subscribe_errors: document.AddItem(Header(5, "Error Subscribing:")) items = map(lambda x: "%s -- %s" % (x[0], x[1]), subscribe_errors) 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) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 62b7335c9..845178137 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -231,7 +231,7 @@ def QuotePeriods(text): # TBD: what other characters should be disallowed? -_badchars = re.compile('[][()<>|;^],') +_badchars = re.compile('[][()<>|;^,]') def ValidateEmail(str): """Verify that the an email address isn't grossly invalid.""" @@ -445,6 +445,14 @@ def GetRandomSeed(): return "%c%c" % tuple(map(mkletter, (chr1, chr2))) +def MakeRandomPassword(length=4): + password = "" + while len(password) < length: + password = password + GetRandomSeed() + password = password[:length] + return password + + def SnarfMessage(msg): if msg.unixfrom: text = msg.unixfrom + string.join(msg.headers, '') + '\n' + msg.body |
