--- mailman.orig/Mailman/MailCommandHandler.py Sat Oct 10 22:39:45 1998 +++ mailman/Mailman/MailCommandHandler.py Mon Oct 12 11:35:01 1998 @@ -51,3 +51,3 @@ } -option_info = { 'digest' : 0, +option_info = { 'nomail' : mm_cfg.DisableDelivery, @@ -60,3 +60,6 @@ class MailCommandHandler: + def __init__(self): + import types + self.enabled = 0 self._response_buffer = '' @@ -73,4 +76,22 @@ 'password' : self.ProcessPasswordCmd, + 'enable' : self.ProcessEnableCmd, + 'disable' : self.ProcessDisableCmd, + 'edit' : self.ProcessEditCmd, + 'put' : self.ProcessPutCmd, + 'setattr': self.ProcessSetattrCmd, } + self._cmd_pooled_dispatch = ( + 'put', + ) self.__NoMailCmdResponse = 0 + self._EditFiles = {} + self._config_info = self.GetConfigInfo() + for i in self._config_info.keys(): + for o in self._config_info[i]: + if type(o) != types.StringType: + if len(o) > 5: + all = o[5] + else: + all = "" + self._EditFiles[o[0]] = [o[1], i, o[4], all] @@ -122,8 +143,11 @@ processed = {} # For avoiding redundancies. - maxlines = mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES + maxcmds = mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES + cmdlines = 0 + cmdmode = 0 + pool = '' for linecount in range(len(lines)): line = string.strip(lines[linecount]) - if not line: + if not line and not cmdmode: continue - if linecount > maxlines: + if cmdlines > maxcmds: self.AddToResponse("\n") @@ -134,2 +158,11 @@ break + if cmdmode: + if line in ['-END-', '-end-']: + self._cmd_dispatch[pooled_cmd](pooled_args, pool, mail) + cmdmode = 0 + pool = '' + else: + pool = pool + lines[linecount] + "\n" + continue + cmdlines = cmdlines + 1 self.AddToResponse("\n>>>> %s" % line) @@ -157,5 +190,14 @@ processed[cmd].append(args) - self._cmd_dispatch[cmd](args, line, mail) + if cmd in self._cmd_pooled_dispatch: + pooled_cmd = cmd + pooled_args = args + pooled_line = line + cmdmode = 1 + else: + self._cmd_dispatch[cmd](args, line, mail) + if cmdmode: + self._cmd_dispatch[pooled_cmd](pooled_args, pool, mail) if not self.__NoMailCmdResponse: self.SendMailCmdResponse(mail) + sys.exit(0) @@ -169,2 +211,129 @@ + def ProcessEnableCmd(self, args, cmdline, mail): + if len(args) <> 1: + self.AddError("Usage: enable ") + self.enabled = 0 + return + try: + self.ConfirmAdminPassword(args[0]) + except Errors.MMBadPasswordError: + self.AddError("Incorrect admin password") + self.enabled = 0 + return + self.enabled = 1 + self.AddToResponse("Admin commands enabled.") + + def ProcessDisableCmd(self, args, cmdline, mail): + self.enabled = 0 + self.AddToResponse("Admin commands disabled.") + + def enableCheck(self): + if not self.enabled: + self.AddError("Enable admin commands first.") + return 0 + return 1 + + def _EditFile(self, fname): + if self._EditFiles[fname][0] in [mm_cfg.Radio, mm_cfg.Toggle]: + if getattr(self, fname): + txt = "on" + else: + txt = "off" + text = "setattr %s=%s" % (fname, txt) + elif self._EditFiles[fname][0] == mm_cfg.EmailList: + text = "put %s\n%s\n-end-" % (fname, string.join(getattr(self, fname),"\n")) + elif self._EditFiles[fname][0] in [mm_cfg.Email, mm_cfg.Host]: + text = "setattr %s=%s" % (fname, getattr(self, fname)) + elif self._EditFiles[fname][0] == mm_cfg.String: + text = "setattr %s=\"%s\"" % (fname, getattr(self, fname)) + elif self._EditFiles[fname][0] == mm_cfg.Number: + text = "setattr %s=%d" % (fname, getattr(self, fname)) + else: + text = "put %s\n%s-end-" % (fname, getattr(self, fname)) + return text + + def ProcessEditCmd(self, args, cmdline, mail): + if not self.enableCheck(): + self.AddError("Admin commands disabled.") + return + if len(args) <> 1: + self.AddError("Usage: edit |all") + return + if self._EditFiles.has_key(args[0]): + self.AddToResponse(self._EditFile(args[0])) + elif args[0] == 'all': + kys = self._EditFiles.keys() + kys.sort() + for i in kys: + self.AddToResponse(self._EditFile(i)) + else: + self.AddError('No such file. You can choose from') + self.AddError("%s" % self._EditFiles.keys()) + + def EmailCheck(self, addr): + try: + valid = Utils.ValidEmail(addr) + if valid: + return 1 + else: + return 0 + except: + return 0 + + def ProcessPutCmd(self, args, pool, mail): + if not self.enableCheck(): + self.AddError("Admin commands disabled.") + return + if len(args) <> 1: + self.AddrError("Usage: put ") + return + if self._EditFiles.has_key(args[0]): + type = self._EditFiles[args[0]][0] + if type == mm_cfg.EmailList: + pool = filter(self.EmailCheck, + map(string.strip, string.split(pool, '\n'))) + elif type != mm_cfg.Text: + self.AddError("Invalid type for %s." % args[0]) + return + setattr(self, args[0], pool) + self.Save() + self.AddToResponse('Succeeeded.') + + + def ProcessSetattrCmd(self, args, cmdline, mail): + if not self.enableCheck(): + self.AddError("Admin commands disabled.") + return + try: + retv = re.match("^\s*\S+\s+([^\s=]+)\s*=\s*\"?(.*[^\"])\"?$", + cmdline) + key, val = retv.group(1, 2) + except: + self.AddError("Usage: setattr =") + return + + if self._EditFiles.has_key(key): + type = self._EditFiles[key][0] + if type != mm_cfg.Text: + if type in [mm_cfg.Radio, mm_cfg.Toggle]: + if val in ["on", "yes", "true", "1", "yep"]: + val = 1 + else: + val = 0 + elif type == mm_cfg.Email: + retv = string.strip(val) + if not self.EmailCheck(val): + self.AddError("Invalid email address %s" % retv) + return + elif type == mm_cfg.Number: + val = eval(val) + if cmdline < 0: + self.AddError("Invalid number %d" % retv) + return + setattr(self, key, val) + self.Save() + self.AddToResponse('%s set to %s.'%(key, val)) + else: + self.AddError("No such file: %s" % key) + def ProcessPasswordCmd(self, args, cmd, mail): @@ -223,2 +392,8 @@ s.AddError("%s: %s" % (option, od[option])) + + sender = self.FindUser(mail.GetSender()) + if not sender: + self.AddError("You aren't subscribed.") + return + if len(args) <> 3: @@ -235,6 +410,2 @@ try: - sender = self.FindUser(mail.GetSender()) - if not sender: - self.AddError("You aren't subscribed.") - return self.ConfirmUserPassword(sender, args[2]) @@ -251,3 +422,4 @@ try: - self.SetUserDigest(mail.GetSender(), args[2], value) + self.ConfirmUserPassword(sender, args[2]) + self.SetUserDigest(mail.GetSender(), value) self.AddToResponse("Succeeded.") @@ -447,42 +619,41 @@ if not address: - subscribe_address = string.lower(mail.GetSender()) + pending_addr = mail.GetSender() else: - subscribe_address = address + pending_addr = address remote = mail.GetSender() - try: - self.AddMember(subscribe_address, password, digest, remote) - except Errors.MMSubscribeNeedsConfirmation: - # - # the confirmation message that's been sent takes place - # of the results of the mail command message - # - self.__NoMailCmdResponse = 1 - except Errors.MMNeedApproval, admin_email: - self.AddToResponse("your subscription request has been forwarded the list " - "administrator\nat %s for review.\n" % admin_email) - except Errors.MMBadEmailError: - self.AddError("Mailman won't accept the given email " - "address as a valid address. \n(Does it " - "have an @ in it???)") - except Errors.MMListNotReady: - self.AddError("The list is not fully functional, and " - "can not accept subscription requests.") - except Errors.MMHostileAddress: - self.AddError("Your subscription is not allowed because\n" - "the email address you gave is insecure.") - except Errors.MMAlreadyAMember: - self.AddError("You are already subscribed!") - except Errors.MMCantDigestError: - self.AddError("No one can subscribe to the digest of this list!") - except Errors.MMMustDigestError: - self.AddError("This list only supports digest subscriptions!") + if pending_addr == self.GetListEmail(): + badremote = "\n\tfrom " + if remote: badremote = badremote + remote + else: badremote = badremote + "unidentified sender" + self.LogMsg("mischief", ("Attempt to self subscribe %s:%s" + % (pending_addr, badremote))) + self.AddApprovalMsg("Attempt to subscribe a list to itself!") + return + if self.FindUser(pending_addr): + self.AddError("%s is already a list member." % pending_addr) + return + cookie = Pending.gencookie() + Pending.add2pending(pending_addr, password, digest, cookie) + if remote == pending_addr: + remote = "" else: - # - # if the list sends a welcome message, we don't need a response - # from the mailcommand handler. - # - if self.send_welcome_msg: - self.__NoMailCmdResponse = 1 - else: - self.AddToResponse("Succeeded") + remote = " from " + remote + text = Utils.maketext( + 'verify.txt', + {'email' : pending_addr, + 'listaddr' : self.GetListEmail(), + 'listname' : self.real_name, + 'listadmin' : self.GetAdminEmail(), + 'cookie' : cookie, + 'remote' : remote, + 'requestaddr' : self.GetRequestEmail(), + }) + self.SendTextToUser( + subject = "%s -- confirmation of subscription -- request %d" % + (self.real_name, cookie), + recipient = pending_addr, + sender = self.GetRequestEmail(), + text = text) + self.__NoMailCmdResponse = 1 + return @@ -500,5 +671,4 @@ return - try: - self.ProcessConfirmation(cookie) - except Errors.MMBadConfirmation: + pending = Pending.get_pending() + if not pending.has_key(cookie): self.AddError("Invalid confirmation number!\n" @@ -506,17 +676,47 @@ " try again.") - except Errors.MMNeedApproval, admin_addr: - self.AddToResponse("your request has been forwarded to the list " - "administrator for approval") - + return + (email_addr, password, digest, ts) = pending[cookie] + if self.open_subscribe: + self.FinishSubscribe(email_addr, password, digest, + approved=1) else: - # - # if the list sends a welcome message, we don't need a response - # from the mailcommand handler. - # - if self.send_welcome_msg: - self.__NoMailCmdResponse = 1 - else: - self.AddToResponse("Succeeded") - + self.FinishSubscribe(email_addr, password, digest) + del pending[cookie] + Pending.set_pending(pending) + def FinishSubscribe(self, addr, password, digest, approved=0): + try: + if approved: + self.ApprovedAddMember(addr, password, digest) + else: + self.AddMember(addr, password, digest) + self.AddToResponse("Succeeded.") + except Errors.MMBadEmailError: + self.AddError("Email address '%s' not accepted by Mailman." % + addr) + except Errors.MMMustDigestError: + self.AddError("List only accepts digest members.") + except Errors.MMCantDigestError: + self.AddError("List doesn't accept digest members.") + except Errors.MMListNotReady: + self.AddError("List is not functional.") + except Errors.MMNeedApproval: + self.AddApprovalMsg("Subscription is pending list admin approval.") + except Errors.MMHostileAddress: + self.AddError("Email address '%s' not accepted by Mailman " + "(insecure address)" % addr) + except Errors.MMAlreadyAMember: + self.AddError("%s is already a list member." % addr) + except: + self.AddError("An unknown Mailman error occured.") + self.AddError("Please forward your request to %s" % + self.GetAdminEmail()) + self.AddError("%s" % sys.exc_type) + self.LogMsg("error", ("%s:\n\t%s.FinishSubscribe() encountered" + " unexpected exception:\n\t'%s', '%s'" + % (__name__, + self._internal_name, + str(sys.exc_info()[0]), + str(sys.exc_info()[1])))) + def AddApprovalMsg(self, cmd): @@ -531,7 +731,40 @@ def ProcessHelpCmd(self, args, cmd, mail): + txt = 'help.txt' + if len(args) >= 1: + if args[0] in ["edit", "put", "setattr"]: + self.Types = { + mm_cfg.Radio: "setattr; yes or no", + mm_cfg.Toggle: "setattr; yes or no", + mm_cfg.Email: "setattr; email", + mm_cfg.String: "setattr; string line", + mm_cfg.Host: "setattr; host name", + mm_cfg.Number: "setattr; number", + mm_cfg.Text: "put; string lines", + mm_cfg.EmailList: "put; list of emails (one in a line)" + } + if len(args) > 1 and args[1] == "all": + all = 1 + else: + all = 0 + text = "Files in %s:" % args[0] + kys = self._EditFiles.keys() + kys.sort() + for i in kys: + type = self.Types[self._EditFiles[i][0]] + txt = Utils.wrap(self._EditFiles[i][2]) + if all and self._EditFiles[i][3] != "": + txt = txt + "\n" + Utils.wrap(self._EditFiles[i][3]) + text = text + "\n\n%s (from %s):\n [%s]\n " % (i, + self._EditFiles[i][1], type) + string.join( + string.split(txt, "\n"), "\n ") + self.AddToResponse(text) + return + if args[0] == 'admin': + txt = 'adminhelp.txt' text = Utils.maketext( - 'help.txt', + txt, {'listname' : self.real_name, 'version' : mm_cfg.VERSION, - 'listinfo_url': self.GetAbsoluteScriptURL('listinfo'), + 'listinfo_url': self.GetAbsoluteScriptURL(mm_cfg.LISTINFO_CGI), + 'admin_url' : self.GetAbsoluteScriptURL(mm_cfg.ADMIN_CGI), 'requestaddr' : self.GetRequestEmail(), --- mailman.orig/Mailman/MailList.py Tue Sep 29 11:52:38 1998 +++ mailman/Mailman/MailList.py Tue Sep 29 11:52:49 1998 @@ -55,4 +55,4 @@ raise Errors.MMUnknownListError, 'list not found: %s' % name - MailCommandHandler.__init__(self) self.InitTempVars(name, lock) + MailCommandHandler.__init__(self) if name: --- mailman.orig/templates/adminhelp.txt Tue Sep 22 14:52:53 1998 +++ mailman/templates/adminhelp.txt Tue Sep 29 10:48:14 1998 @@ -0,0 +1,62 @@ +Admin help for %(listname)s mailing list: + +This is administration email command help for version %(version)s of the +"Mailman" list manager. The following describes commands you can send to +get information about and control your Mailman list. A command can be in +the subject line or in the body of the message. + +The basics of email commands are described in the ordinary help. You can +retrieve it with 'help' command without arguments. + +Note that much of the following can also be accomplished via the web, +at: + + %(admin_url)s + +Administration commands should be sent to the *-request address of the +particular list, eg. for the 'mailman' list, use 'mailman-request@...'. + +About the descriptions - words in "<>"s signify REQUIRED items and +words in "[]" denote OPTIONAL items. Do not include the "<>"s or +"[]"s when you use the commands. + +The following commands are available for administration: + + enable + Enable the administration command set. It should be the + first command you give. + + disable + Disable the administration command set. It's automatic when + the parsing is finished. + + edit + or + edit all + Retrieve for editing or view the contents. You'll get + the suitable 'put' command with a trailing '-end-' line or a + 'setattr' command. 'edit all' will send you all of settings. + See also 'help '. + + put + Refreshes . This is a pool command. This means you + have to write the contents after 'put'. The setting lasts at the + first '-end-' line. See also 'help '. + + setattr = + This is the same as 'put', but for the single line settings. + You can use double quotes (") to enclose , but you + don't have to escape double quotes within . + + For example: + setattr subject_prefix="[MailMan] " + + help [all] + You can get help on 'edit', 'put' or 'setattr' commands. If you + want to get it in details, use the 'all' option. This help + shows you the acceptable contents of an option too. + + -end- + Stop processing pool and run the pool command. + +Commands should be sent to %(requestaddr)s.