def gen_dot(): d = Dot() nodes = dict() login = read_ssv_file("vpopmail.login") db = MySQLdb.connect(host="localhost", user=login[0], passwd=login[2], db=login[1]) c = db.cursor() c.execute("SELECT alias, valias_line FROM valias WHERE domain=%s", (MAILDOMAIN,)) for alias, target in c.fetchall(): assert target[0] == "&" target = target[1:] alias += "@" + MAILDOMAIN if not alias in nodes: nodes[alias] = Node(alias) d.add_node(nodes[alias]) if not target in nodes: nodes[target] = Node(target) d.add_node(nodes[target]) d.add_edge(Edge(nodes[alias], nodes[target])) for list in Utils.list_names(): if list == "plukdenacht2008": continue source = list + "@" + LISTDOMAIN if not source in nodes: nodes[source] = Node(source) d.add_node(nodes[source]) m = MailList.MailList(list, lock=False) for member in m.members: if not member in nodes: nodes[member] = Node(member) d.add_node(nodes[member]) d.add_edge(Edge(nodes[source], nodes[member])) d.write("the.dot")
def process(mlist, msg, msgdata): # Extract the sender's address and find them in the user database sender = msgdata.get('original_sender', msg.get_sender()) try: ack = mlist.getMemberOption(sender, mm_cfg.AcknowledgePosts) if not ack: return except Errors.NotAMemberError: return # Okay, they want acknowledgement of their post. Give them their original # subject. BAW: do we want to use the decoded header? origsubj = msgdata.get('origsubj', msg.get('subject', _('(no subject)'))) # Get the user's preferred language lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) # Now get the acknowledgement template realname = mlist.real_name text = Utils.maketext( 'postack.txt', {'subject' : Utils.oneline(origsubj, Utils.GetCharSet(lang)), 'listname' : realname, 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), 'optionsurl' : mlist.GetOptionsURL(sender, absolute=1), }, lang=lang, mlist=mlist, raw=1) # Craft the outgoing message, with all headers and attributes # necessary for general delivery. Then enqueue it to the outgoing # queue. subject = _('%(realname)s post acknowledgement') usermsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), subject, text, lang) usermsg.send(mlist)
def do_command(self, cmd, args=None): if args is None: args = () # Try to import a command handler module for this command modname = 'Mailman.Commands.cmd_' + cmd try: __import__(modname) handler = sys.modules[modname] # ValueError can be raised if cmd has dots in it. except (ImportError, ValueError): # If we're on line zero, it was the Subject: header that didn't # contain a command. It's possible there's a Re: prefix (or # localized version thereof) on the Subject: line that's messing # things up. Pop the prefix off and try again... once. # # If that still didn't work it isn't enough to stop processing. # BAW: should we include a message that the Subject: was ignored? if not self.subjcmdretried and args: self.subjcmdretried += 1 cmd = args.pop(0) return self.do_command(cmd, args) return self.lineno <> 0 # with Dlists, we don't allow email subscription if DlistUtils.enabled(self.mlist) and (cmd == 'subscribe' or cmd == 'join'): realname = self.mlist.real_name domain = Utils.get_domain() self.results.append(Utils.wrap(_("""\ This list cannot be subscribed to via email. Please use the website at http://%(domain)s/mailman/listinfo/%(realname)s . """))) return self.lineno <> 0 # superstitious behavior as they do it above return handler.process(self, args)
def __sendAdminBounceNotice(self, member, msg): # BAW: This is a bit kludgey, but we're not providing as much # information in the new admin bounce notices as we used to (some of # it was of dubious value). However, we'll provide empty, strange, or # meaningless strings for the unused %()s fields so that the language # translators don't have to provide new templates. siteowner = Utils.get_site_email(self.host_name) text = Utils.maketext( "bounce.txt", { "listname": self.real_name, "addr": member, "negative": "", "did": _("disabled"), "but": "", "reenable": "", "owneraddr": siteowner, }, mlist=self, ) subject = _("Bounce action notification") umsg = Message.UserNotification(self.GetOwnerEmail(), siteowner, subject, lang=self.preferred_language) # BAW: Be sure you set the type before trying to attach, or you'll get # a MultipartConversionError. umsg.set_type("multipart/mixed") umsg.attach(MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language))) if isinstance(msg, StringType): umsg.attach(MIMEText(msg)) else: umsg.attach(MIMEMessage(msg)) umsg.send(self)
def send_response(self): # Helper def indent(lines): return [' ' + line for line in lines] # Quick exit for some commands which don't need a response if not self.respond: return resp = [Utils.wrap(_("""\ The results of your email command are provided below. Attached is your original message. """))] if self.results: resp.append(_('- Results:')) resp.extend(indent(self.results)) # Ignore empty lines unprocessed = [line for line in self.commands[self.lineno:] if line and line.strip()] if unprocessed: resp.append(_('\n- Unprocessed:')) resp.extend(indent(unprocessed)) if not unprocessed and not self.results: # The user sent an empty message; return a helpful one. resp.append(Utils.wrap(_("""\ No commands were found in this message. To obtain instructions, send a message containing just the word "help". """))) if self.ignored: resp.append(_('\n- Ignored:')) resp.extend(indent(self.ignored)) resp.append(_('\n- Done.\n\n')) # Encode any unicode strings into the list charset, so we don't try to # join unicode strings and invalid ASCII. charset = Utils.GetCharSet(self.msgdata['lang']) encoded_resp = [] for item in resp: if isinstance(item, UnicodeType): item = item.encode(charset, 'replace') encoded_resp.append(item) results = MIMEText(NL.join(encoded_resp), _charset=charset) # Safety valve for mail loops with misconfigured email 'bots. We # don't respond to commands sent with "Precedence: bulk|junk|list" # unless they explicitly "X-Ack: yes", but not all mail 'bots are # correctly configured, so we max out the number of responses we'll # give to an address in a single day. # # BAW: We wait until now to make this decision since our sender may # not be self.msg.get_sender(), but I'm not sure this is right. recip = self.returnaddr or self.msg.get_sender() if not self.mlist.autorespondToSender(recip, self.msgdata['lang']): return msg = Message.UserNotification( recip, self.mlist.GetBouncesEmail(), _('The results of your email commands'), lang=self.msgdata['lang']) msg.set_type('multipart/mixed') msg.attach(results) orig = MIMEMessage(self.msg) msg.attach(orig) msg.send(self.mlist)
def loginpage(mlist, scriptname, msg='', frontpage=None): url = mlist.GetScriptURL(scriptname) if frontpage: actionurl = url else: actionurl = Utils.GetRequestURI(url) if msg: msg = FontAttr(msg, color='#ff0000', size='+1').Format() # give an HTTP 401 for authentication failure print 'Status: 401 Unauthorized' 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, }, mlist=mlist) print mlist.GetMailmanFooter()
def _cleanup(self): """Clean up upon exit from the main processing loop. Called when the Runner's main loop is stopped, this should perform any necessary resource deallocation. Its return value is irrelevant. """ Utils.reap(self._kids)
def errcheck (self, action): """Performs all error checks. Returns None is all's good. Otherwise returns a string with error message.""" if not can_create_lists(self.curr_user): return 'You are not authorized to creates lists on this server' if len(self.owner) <= 0: return 'Cannot create list without a owner.' if self.ln == '': return 'You forgot to enter the list name' if '@' in self.ln: return 'List name must not include "@": %s' % self.safeln if action == 'create' and Utils.list_exists(self.ln): return 'List already exists: %s' % self.safe_ln if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ not mm_cfg.VIRTUAL_HOSTS.has_key(self.hn): safehostname = Utils.websafe(self.hn) return 'Unknown virtual host: %s' % safehostname return None
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 = _("<missing>") 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(border=3, width="100%") table.AddRow([Center(Bold(_("Topic filter details")))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, bgcolor=mm_cfg.WEB_SUBHEADER_COLOR) table.AddRow([Bold(Label(_("Name:"))), Utils.websafe(name)]) table.AddRow([Bold(Label(_("Pattern (as regexp):"))), "<pre>" + Utils.websafe(pattern) + "</pre>"]) table.AddRow([Bold(Label(_("Description:"))), Utils.websafe(description)]) # Make colors look nice for row in range(1, 4): table.AddCellInfo(row, 0, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) options_page(mlist, doc, user, cpuser, userlang, table.Format()) print doc.Format()
def scrub_msg822(self, part): # submessage submsg = part.get_payload(0) omask = os.umask(002) try: url = save_attachment(self.mlist, part, self.dir) finally: os.umask(omask) subject = submsg.get('subject', _('no subject')) subject = Utils.oneline(subject, self.lcset) date = submsg.get('date', _('no date')) who = submsg.get('from', _('unknown sender')) who = Utils.oneline(who, self.lcset) size = len(str(submsg)) self.msgtexts.append(unicode(_("""\ An embedded message was scrubbed... From: %(who)s Subject: %(subject)s Date: %(date)s Size: %(size)s URL: %(url)s """), self.lcset)) # Replace this part because subparts should not be walk()-ed. del part['content-type'] part.set_payload('blah blah', 'us-ascii')
def create(self, email): if self.exists: raise ListAlreadyExists langs = [mm_cfg.DEFAULT_SERVER_LANGUAGE] pw = Utils.MakeRandomPassword() pw_hashed = Utils.sha_new(pw).hexdigest() urlhost = mm_cfg.DEFAULT_URL_HOST host_name = mm_cfg.DEFAULT_EMAIL_HOST web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost # TODO: Add some atomicity. We should roll back changes using # a try/else if something (like MTA alias update) fails # before the function terminates. try: oldmask = os.umask(002) self.mlist.Create(self.name, email, pw_hashed, langs=langs, emailhost=host_name, urlhost=urlhost) self.mlist.preferred_language = langs[0] # Reply-To set to list address self.mlist.reply_goes_to_list = 2 self.mlist.reply_to_address = "%s@%s" % (self.list, self.domain) # Allow messages from listname@domain self.mlist.acceptable_aliases = "%s@%s\n" % (self.list, self.domain) self.mlist.subject_prefix = "[%s] " % (self.list) self.mlist.msg_footer = "" self.mlist.subscribe_policy = 2 # Confirm and approve self.mlist.max_message_size = 20480 # 20M self.mlist.Save() finally: os.umask(oldmask) self.mlist.Unlock() if mm_cfg.MTA: modname = 'Mailman.MTA.' + mm_cfg.MTA __import__(modname) sys.modules[modname].create(self.mlist) siteowner = Utils.get_site_email(self.mlist.host_name, 'owner') text = Utils.maketext( 'newlist.txt', {'listname' : self.name, 'password' : pw, 'admin_url' : self.mlist.GetScriptURL('admin', absolute=1), 'listinfo_url': self.mlist.GetScriptURL('listinfo', absolute=1), 'requestaddr' : self.mlist.GetRequestEmail(), 'siteowner' : siteowner, }, mlist=self.mlist) msg = Message.UserNotification(email, siteowner, 'Your new mailing list: %s' % self.name, text, self.mlist.preferred_language) msg.send(self.mlist)
def do_reject(mlist): listowner = mlist.GetOwnerEmail() if mlist.nonmember_rejection_notice: raise Errors.RejectMessage, \ Utils.wrap(_(mlist.nonmember_rejection_notice)) else: raise Errors.RejectMessage, Utils.wrap(_("""\ You are not allowed to post to this mailing list, and your message has been automatically rejected. If you think that your messages are being rejected in error, contact the mailing list owner at %(listowner)s."""))
def do_reject(mlist): listowner = mlist.GetOwnerEmail() if mlist.nonmember_rejection_notice: raise Errors.RejectMessage, \ Utils.wrap(_(mlist.nonmember_rejection_notice)) else: raise Errors.RejectMessage, Utils.wrap(_("""\ Your message has been rejected, probably because you are not subscribed to the mailing list and the list's policy is to prohibit non-members from posting to it. If you think that your messages are being rejected in error, contact the mailing list owner at %(listowner)s."""))
def openidreg_overview(lang, 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(lang) doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) legend = _(" OpenID Registeration for Systers Mailing Lists") doc.SetTitle(legend) table = Table(border=0, width="100%") 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. if msg: greeting = FontAttr(msg, color="ff5060", size="+1") else: greeting = FontAttr(_('Welcome!'), size='+2') welcome = [greeting] mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() # set up some local variables adj = msg and _('right') or '' siteowner = Utils.get_site_email() welcome.extend( (_(''' This is the Systers OpenID registeration form . To enable your systers account fill in the following entries. <p>or Go back to the listinfo page if already using it '''), Link(Utils.ScriptURL('listinfo'), _('the mailing lists overview page')), _(''' <p>If you are having trouble using the lists, please contact '''), Link('mailto:' + siteowner, siteowner), '.<p>', FormatOpenIDLogin(), '<p>')) table.AddRow([apply(Container, welcome)]) table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) doc.AddItem(table) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format()
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(border=2) table.AddRow([Center(Bold(_('User address/name'))), Center(Bold(_('Your decision'))), Center(Bold(_('Reason for refusal'))) ]) # Alphabetical order by email address byaddrs = {} for id in pendingunsubs: addr = mlist.GetRecord(id) byaddrs.setdefault(addr, []).append(id) addrs = byaddrs.keys() addrs.sort() num = 0 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 num += 1 # While the address may be a unicode, it must be ascii paddr = addr.encode('us-ascii', 'replace') table.AddRow(['%s<br><em>%s</em>' % (paddr, 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 num > 0: form.AddItem('<hr>') form.AddItem(Center(Header(2, _('Unsubscription Requests')))) form.AddItem(table) return num
def __init__(self, whichq, slice=None, numslices=1, recover=False): self._whichq = whichq # Create the directory if it doesn't yet exist. Utils.makedirs(self._whichq, 0770) # Fast track for no slices self._lower = None self._upper = None # BAW: test performance and end-cases of this algorithm if numslices <> 1: self._lower = ((shamax + 1) * slice) / numslices self._upper = (((shamax + 1) * (slice + 1)) / numslices) - 1 if recover: self.recover_backup_files()
def SendSubscribeAck(self, name, password, digest, text=""): pluser = self.getMemberLanguage(name) # Need to set this here to get the proper l10n of the Subject: i18n.set_language(pluser) if self.welcome_msg: welcome = Utils.wrap(self.welcome_msg) + "\n" else: welcome = "" if self.umbrella_list: addr = self.GetMemberAdminEmail(name) umbrella = Utils.wrap( _( """\ Note: Since this is a list of mailing lists, administrative notices like the password reminder will be sent to your membership administrative address, %(addr)s.""" ) ) else: umbrella = "" # get the text from the template text += Utils.maketext( "subscribeack.txt", { "real_name": self.real_name, "host_name": self.host_name, "welcome": welcome, "umbrella": umbrella, "emailaddr": self.GetListEmail(), "listinfo_url": self.GetScriptURL("listinfo", absolute=True), "optionsurl": self.GetOptionsURL(name, absolute=True), "password": password, "user": self.getMemberCPAddress(name), }, lang=pluser, mlist=self, ) if digest: digmode = _(" (Digest mode)") else: digmode = "" realname = self.real_name msg = Message.UserNotification( self.GetMemberAdminEmail(name), self.GetRequestEmail(), _('Welcome to the "%(realname)s" mailing list%(digmode)s'), text, pluser, ) msg["X-No-Archive"] = "yes" msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES)
def show_pending_subs(mlist, form): # Add the subscription request section pendingsubs = mlist.GetSubscriptionIds() if not pendingsubs: return 0 form.AddItem('<hr>') form.AddItem(Center(Header(2, _('Subscription Requests')))) table = Table(border=2) table.AddRow([Center(Bold(_('Address/name'))), Center(Bold(_('Your decision'))), Center(Bold(_('Reason for refusal'))) ]) # Alphabetical order by email address byaddrs = {} for id in pendingsubs: addr = mlist.GetRecord(id)[1] byaddrs.setdefault(addr, []).append(id) addrs = byaddrs.items() addrs.sort() num = 0 for addr, ids in addrs: # 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 += ('<br>' + '<label>' + CheckBox('ban-%d' % id, 1).Format() + ' ' + _('Permanently ban from this list') + '</label>') # While the address may be a unicode, it must be ascii paddr = addr.encode('us-ascii', 'replace') table.AddRow(['%s<br><em>%s</em>' % (paddr, Utils.websafe(fullname)), radio, TextBox('comment-%d' % id, size=40) ]) num += 1 if num > 0: form.AddItem(table) return num
def __init__(self, text=''): from Mailman.pythonlib.StringIO import StringIO # NOTE: text if supplied must begin with valid rfc822 headers. It can # also begin with the body of the message but in that case you better # make sure that the first line does NOT contain a colon! Message.__init__(self, StringIO(text)) # RFC 2822 requires a Date: header, and while most MTAs add one if # it's missing, qmail does not. if not self.get('date'): self['Date'] = Utils.formatdate(localtime=1) # RFC 2822 recommends a Message-ID: header, and while most MTAs add # one if it's missing, qmail does not. if not self.get('message-id'): self['Message-ID'] = Utils.make_msgid(idstring='Mailman')
def lists_of_member(safeuser): hostname = mm_cfg.DEFAULT_URL_HOST onlists = [] for listname in Utils.list_names(): # The current list will always handle things in the mainline if listname == Utils.list_names(): continue glist = MailList.MailList(listname, lock=0) if glist.host_name <> hostname: continue if not glist.isMember(safeuser): continue onlists.append(glist) return onlists
def finish(self, filebase, preserve=False): bakfile = os.path.join(self._whichq, filebase + '.bak') try: if preserve: psvfile = os.path.join(config.SHUNTQUEUE_DIR, filebase + '.psv') # Create the directory if it doesn't yet exist. Utils.makedirs(config.SHUNTQUEUE_DIR, 0770) os.rename(bakfile, psvfile) else: os.unlink(bakfile) except EnvironmentError, e: elog.exception('Failed to unlink/preserve backup file: %s', bakfile)
def quote(s, is_header=False): if is_header: h = Utils.oneline(s, 'utf-8') else: h = s # Remove illegal XML characters # Try to decode UTF-8, so that Utils.uquote can escape multibyte characters # correctly. try: hclean = h.decode('utf-8') hclean = u''.join(re.split(u'[\x00-\x08\x0B-\x1f]+', hclean)) except UnicodeDecodeError: hclean = ''.join(re.split('[\x00-\x08\x0B-\x1f]+', h)) return Utils.uquote(hclean.replace('&', '&').replace('>', '>').replace('<', '<'))
def process(res, args): mlist = res.mlist if args: res.results.append(_('Usage:')) res.results.append(gethelp(mlist)) return STOP hostname = mlist.host_name res.results.append(_('Public mailing lists at %(hostname)s:')) lists = Utils.list_names() lists.sort() i = 1 for listname in lists: if listname == mlist.internal_name(): xlist = mlist else: xlist = MailList(listname, lock=0) # We can mention this list if you already know about it if not xlist.advertised and xlist is not mlist: continue # Skip the list if it isn't in the same virtual domain. BAW: should a # message to the site list include everything regardless of domain? if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ xlist.host_name <> mlist.host_name: continue realname = xlist.real_name description = xlist.description or _('n/a') requestaddr = xlist.GetRequestEmail() if i > 1: res.results.append('') res.results.append(_('%(i)3d. List name: %(realname)s')) res.results.append(_(' Description: %(description)s')) res.results.append(_(' Requests to: %(requestaddr)s')) i += 1
def _addlist(mlist, fp): # Set up the mailman-loop address loopaddr = Utils.ParseEmail(Utils.get_site_email(extra='loop'))[0] loopmbox = os.path.join(mm_cfg.DATA_DIR, 'owner-bounces.mbox') # Seek to the end of the text file, but if it's empty write the standard # disclaimer, and the loop catch address. fp.seek(0, 2) if not fp.tell(): print >> fp, """\ # This file is generated by Mailman, and is kept in sync with the # binary hash file aliases.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE # unless you know what you're doing, and can keep the two files properly # in sync. If you screw it up, you're on your own. """ print >> fp, '# The ultimate loop stopper address' print >> fp, '%s: %s' % (loopaddr, loopmbox) print >> fp # Bootstrapping. bin/genaliases must be run before any lists are created, # but if no lists exist yet then mlist is None. The whole point of the # exercise is to get the minimal aliases.db file into existance. if mlist is None: return listname = mlist.internal_name() fieldsz = len(listname) + len('-unsubscribe') # The text file entries get a little extra info print >> fp, '# STANZA START:', listname print >> fp, '# CREATED:', time.ctime(time.time()) # Now add all the standard alias entries for k, v in makealiases(listname): # Format the text file nicely print >> fp, k + ':', ((fieldsz - len(k)) * ' ') + v # Finish the text file stanza print >> fp, '# STANZA END:', listname print >> fp
def get_lists(userdesc, perms, vhost, email=None): """ List available lists for the given vhost """ if email is None: udesc = userdesc else: udesc = UserDesc(email.lower(), email.lower(), None, 0) prefix = vhost.lower()+VHOST_SEP names = Utils.list_names() names.sort() result = [] for name in names: if not name.startswith(prefix): continue try: mlist = MailList.MailList(name, lock=0) except: continue try: details = get_list_info(udesc, perms, mlist, (email is None and vhost == PLATAL_DOMAIN)) if details is not None: result.append(details[0]) except Exception, e: sys.stderr.write('Can\'t get list %s: %s\n' % (name, str(e))) continue
def decorate(mlist, template, what, extradict=None): # `what' is just a descriptive phrase used in the log message # # BAW: We've found too many situations where Python can be fooled into # interpolating too much revealing data into a format string. For # example, a footer of "% silly %(real_name)s" would give a header # containing all list attributes. While we've previously removed such # really bad ones like `password' and `passwords', it's much better to # provide a whitelist of known good attributes, then to try to remove a # blacklist of known bad ones. d = SafeDict({'real_name' : mlist.real_name, 'list_name' : mlist.internal_name(), # For backwards compatibility '_internal_name': mlist.internal_name(), 'host_name' : mlist.host_name, 'web_page_url' : mlist.web_page_url, 'description' : mlist.description, 'info' : mlist.info, 'cgiext' : mm_cfg.CGIEXT, }) if extradict is not None: d.update(extradict) # Using $-strings? if getattr(mlist, 'use_dollar_strings', 0): template = Utils.to_percent(template) # Interpolate into the template try: text = re.sub(r'(?m)(?<!^--) +(?=\n)', '', re.sub(r'\r\n', r'\n', template % d)) except (ValueError, TypeError), e: syslog('error', 'Exception while calculating %s:\n%s', what, e) text = template
def _addvirtual(mlist, fp): listname = mlist.internal_name() fieldsz = len(listname) + len('-unsubscribe') hostname = mlist.host_name # Set up the mailman-loop address loopaddr = Utils.get_site_email(mlist.host_name, extra='loop') loopdest = Utils.ParseEmail(loopaddr)[0] # Seek to the end of the text file, but if it's empty write the standard # disclaimer, and the loop catch address. fp.seek(0, 2) if not fp.tell(): print >> fp, """\ # This file is generated by Mailman, and is kept in sync with the binary hash # file virtual-mailman.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you # know what you're doing, and can keep the two files properly in sync. If you # screw it up, you're on your own. # # Note that you should already have this virtual domain set up properly in # your Postfix installation. See README.POSTFIX for details. # LOOP ADDRESSES START %s\t%s # LOOP ADDRESSES END """ % (loopaddr, loopdest) # The text file entries get a little extra info print >> fp, '# STANZA START:', listname print >> fp, '# CREATED:', time.ctime(time.time()) # Now add all the standard alias entries for k, v in makealiases(listname): fqdnaddr = '%s@%s' % (k, hostname) # Format the text file nicely print >> fp, fqdnaddr, ((fieldsz - len(k)) * ' '), k # Finish the text file stanza print >> fp, '# STANZA END:', listname print >> fp
def __init__ (self): HTMLAction.__init__(self, "ctl-listadmin.html") self._ln = self.cgival('lc_name').lower() self._priv = self.cgival('lc_private') != '' self._safelin = Utils.websafe(self.ln) self._pw = mm_cfg.SSO_STOCK_ADMIN_PWD self._owner = self.get_owners() self._hn = Utils.get_domain() self._eh = mm_cfg.VIRTUAL_HOSTS.get(self.hn, mm_cfg.DEFAULT_EMAIL_HOST) self._ml = None self._langs = [mm_cfg.DEFAULT_SERVER_LANGUAGE] self._moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION self._notify = 1 self._info = self.cgival('lc_info') self._welcome = self.cgival('lc_welcome') self._desc = self.cgival('lc_desc')
def kill(userdesc, perms, vhost, forlife, promo, del_from_promo): """ Remove a user from all the lists. Args: forlife: the user's forlife email promo: the user's promo, if any (format: X2006) del_from_promo: bool, whether to delete from the promo lists as well. """ exclude = [] if promo and promo[0] == 'X' and not del_from_promo: exclude.append(PLATAL_DOMAIN + VHOST_SEP + 'promo' + promo[1:]) for list in Utils.list_names(): if list in exclude: continue try: mlist = MailList.MailList(list, lock=0) except: continue try: mlist.Lock() mlist.ApprovedDeleteMember(forlife, None, 0, 0) mlist.Save() mlist.Unlock() except: mlist.Unlock() return 1
def handler (self, parts): lists = [] for name, mlist in self.all_mls.iteritems(): members = mlist.getRegularMemberKeys() subscribed = True if self.curr_user in members else False if not mlist.advertised and not subscribed: continue lists.append({'script_url' : mlist.GetScriptURL('listinfo'), 'real_name' : mlist.real_name, 'description' : Utils.websafe(mlist.description), 'subscribed' : subscribed, 'owners' : ', '.join(mlist.owner), 'owner-email' : mlist.GetOwnerEmail(), 'advertised' : mlist.advertised, }) self.kwargs_add('lists', lists) if len(parts) > 0: try: self.add_req_ln_details(parts[0].strip()) except: self.kwargs_add('vl_ln', None) else: self.kwargs_add('vl_ln', None) self.render()
def loginpage(mlist, doc, user, lang): realname = mlist.real_name actionurl = mlist.GetScriptURL('options') if user is None: title = _('%(realname)s list: member options login page') extra = _('email address and ') else: safeuser = Utils.websafe(user) title = _('%(realname)s list: member options for user %(safeuser)s') obuser = Utils.ObscureEmail(user) extra = '' # Set up the title doc.SetTitle(title) # We use a subtable here so we can put a language selection box in table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) # If only one language is enabled for this mailing list, omit the choice # buttons. table.AddRow([Center(Header(2, title))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) if len(mlist.GetAvailableLanguages()) > 1: langform = Form(actionurl) langform.AddItem(SubmitButton('displang-button', _('View this page in'))) langform.AddItem(mlist.GetLangSelectBox(lang)) if user: langform.AddItem(Hidden('email', user)) table.AddRow([Center(langform)]) doc.AddItem(table) # Preamble # Set up the login page form = Form(actionurl) form.AddItem(Hidden('language', lang)) table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) table.AddRow([_("""In order to change your membership option, you must first log in by giving your %(extra)smembership 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 <em>Unsubscribe</em> button and a confirmation message will be sent to you. <p><strong><em>Important:</em></strong> From this point on, you must have cookies enabled in your browser, otherwise none of your changes will take effect. """)]) # Password and login button ptable = Table(width='50%', border=0, cellspacing=4, cellpadding=5) if user is None: ptable.AddRow([Label(_('Email address:')), TextBox('email', size=20)]) else: ptable.AddRow([Hidden('email', user)]) ptable.AddRow([Label(_('Password:'******'password', size=20)]) ptable.AddRow([Center(SubmitButton('login', _('Log in')))]) ptable.AddCellInfo(ptable.GetCurrentRowIndex(), 0, colspan=2) table.AddRow([Center(ptable)]) # Unsubscribe section table.AddRow([Center(Header(2, _('Unsubscribe')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) table.AddRow([_("""By clicking on the <em>Unsubscribe</em> 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).""")]) table.AddRow([Center(SubmitButton('login-unsub', _('Unsubscribe')))]) # Password reminder section table.AddRow([Center(Header(2, _('Password reminder')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) table.AddRow([_("""By clicking on the <em>Remind</em> button, your password will be emailed to you.""")]) table.AddRow([Center(SubmitButton('login-remind', _('Remind')))]) # Finish up glomming together the login page form.AddItem(table) doc.AddItem(form) doc.AddItem(mlist.GetMailmanFooter())
doc.AddItem(MailmanLogo()) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'options: 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. Likewise if we're # coming from anywhere else. Only issue the error if we came # via one of our buttons. if (cgidata.getvalue('login') or cgidata.getvalue('login-unsub') or cgidata.getvalue('login-remind')): doc.addError(_('No address given')) loginpage(mlist, doc, None, language)
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) replacements['<mm-results>'] = Bold(FontSize('+1', message)).Format() replacements['<mm-digest-radio-button>'] = mlist.FormatOptionButton( mm_cfg.Digests, 1, user) replacements['<mm-undigest-radio-button>'] = mlist.FormatOptionButton( mm_cfg.Digests, 0, user) replacements['<mm-plain-digests-button>'] = mlist.FormatOptionButton( mm_cfg.DisableMime, 1, user) replacements['<mm-mime-digests-button>'] = mlist.FormatOptionButton( mm_cfg.DisableMime, 0, user) replacements['<mm-global-mime-button>'] = ( CheckBox('mime-globally', 1, checked=0).Format()) replacements['<mm-delivery-enable-button>'] = mlist.FormatOptionButton( mm_cfg.DisableDelivery, 0, user) replacements['<mm-delivery-disable-button>'] = mlist.FormatOptionButton( mm_cfg.DisableDelivery, 1, user) replacements['<mm-disabled-notice>'] = mlist.FormatDisabledNotice(user) replacements['<mm-dont-ack-posts-button>'] = mlist.FormatOptionButton( mm_cfg.AcknowledgePosts, 0, user) replacements['<mm-ack-posts-button>'] = mlist.FormatOptionButton( mm_cfg.AcknowledgePosts, 1, user) replacements['<mm-receive-own-mail-button>'] = mlist.FormatOptionButton( mm_cfg.DontReceiveOwnPosts, 0, user) replacements['<mm-dont-receive-own-mail-button>'] = ( mlist.FormatOptionButton(mm_cfg.DontReceiveOwnPosts, 1, user)) replacements['<mm-dont-get-password-reminder-button>'] = ( mlist.FormatOptionButton(mm_cfg.SuppressPasswordReminder, 1, user)) replacements['<mm-get-password-reminder-button>'] = ( mlist.FormatOptionButton(mm_cfg.SuppressPasswordReminder, 0, user)) replacements['<mm-public-subscription-button>'] = ( mlist.FormatOptionButton(mm_cfg.ConcealSubscription, 0, user)) replacements['<mm-hide-subscription-button>'] = mlist.FormatOptionButton( mm_cfg.ConcealSubscription, 1, user) replacements['<mm-dont-receive-duplicates-button>'] = ( mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 1, user)) replacements['<mm-receive-duplicates-button>'] = ( mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 0, user)) replacements['<mm-unsubscribe-button>'] = ( mlist.FormatButton('unsub', _('Unsubscribe')) + '<br>' + CheckBox('unsubconfirm', 1, checked=0).Format() + _('<em>Yes, I really want to unsubscribe</em>')) replacements['<mm-new-pass-box>'] = mlist.FormatSecureBox('newpw') replacements['<mm-confirm-pass-box>'] = mlist.FormatSecureBox('confpw') replacements['<mm-change-pass-button>'] = ( mlist.FormatButton('changepw', _("Change My Password"))) replacements['<mm-other-subscriptions-submit>'] = ( mlist.FormatButton('othersubs', _('List my other subscriptions'))) replacements['<mm-form-start>'] = ( mlist.FormatFormStart('options', user)) replacements['<mm-user>'] = user replacements['<mm-presentable-user>'] = presentable_user replacements['<mm-email-my-pw>'] = mlist.FormatButton( 'emailpw', (_('Email My Password To Me'))) replacements['<mm-umbrella-notice>'] = ( mlist.FormatUmbrellaNotice(user, _("password"))) replacements['<mm-logout-button>'] = ( mlist.FormatButton('logout', _('Log out'))) replacements['<mm-options-submit-button>'] = mlist.FormatButton( 'options-submit', _('Submit My Changes')) replacements['<mm-global-pw-changes-button>'] = ( CheckBox('pw-globally', 1, checked=0).Format()) replacements['<mm-global-deliver-button>'] = ( CheckBox('deliver-globally', 1, checked=0).Format()) replacements['<mm-global-remind-button>'] = ( CheckBox('remind-globally', 1, checked=0).Format()) replacements['<mm-global-nodupes-button>'] = ( 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['<mm-pending-days>'] = _('%(days)d %(units)s') replacements['<mm-new-address-box>'] = mlist.FormatBox('new-address') replacements['<mm-confirm-address-box>'] = mlist.FormatBox( 'confirm-address') replacements['<mm-change-address-button>'] = mlist.FormatButton( 'change-of-address', _('Change My Address and Name')) replacements['<mm-global-change-of-address>'] = CheckBox( 'changeaddr-globally', 1, checked=0).Format() replacements['<mm-fullname-box>'] = 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: if emptyflag: continue 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 = _('<em>No topics defined</em>') replacements['<mm-topics>'] = topicsfield replacements['<mm-suppress-nonmatching-topics>'] = ( mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 0, user)) replacements['<mm-receive-nonmatching-topics>'] = ( mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 1, user)) if cpuser is not None: replacements['<mm-case-preserved-user>'] = _(''' You are subscribed to this list with the case-preserved address <em>%(cpuser)s</em>.''') else: replacements['<mm-case-preserved-user>'] = '' doc.AddItem(mlist.ParseTags('options.html', replacements, userlang))
def process_message(self, peer, mailfrom, rcpttos, data): from io import StringIO from Mailman import Utils from Mailman import Message from Mailman import MailList # If the message is to a Mailman mailing list, then we'll invoke the # Mailman script directly, without going through the real smtpd. # Otherwise we'll forward it to the local proxy for disposition. listnames = [] for rcpt in rcpttos: local = rcpt.lower().split('@')[0] # We allow the following variations on the theme # listname # listname-admin # listname-owner # listname-request # listname-join # listname-leave parts = local.split('-') if len(parts) > 2: continue listname = parts[0] if len(parts) == 2: command = parts[1] else: command = '' if not Utils.list_exists(listname) or command not in ( '', 'admin', 'owner', 'request', 'join', 'leave'): continue listnames.append((rcpt, listname, command)) # Remove all list recipients from rcpttos and forward what we're not # going to take care of ourselves. Linear removal should be fine # since we don't expect a large number of recipients. for rcpt, listname, command in listnames: rcpttos.remove(rcpt) # If there's any non-list destined recipients left, print('forwarding recips:', ' '.join(rcpttos), file=DEBUGSTREAM) if rcpttos: refused = self._deliver(mailfrom, rcpttos, data) # TBD: what to do with refused addresses? print('we got refusals:', refused, file=DEBUGSTREAM) # Now deliver directly to the list commands mlists = {} s = StringIO(data) msg = Message.Message(s) # These headers are required for the proper execution of Mailman. All # MTAs in existence seem to add these if the original message doesn't # have them. if not msg.get('from'): msg['From'] = mailfrom if not msg.get('date'): msg['Date'] = time.ctime(time.time()) for rcpt, listname, command in listnames: print('sending message to', rcpt, file=DEBUGSTREAM) mlist = mlists.get(listname) if not mlist: mlist = MailList.MailList(listname, lock=0) mlists[listname] = mlist # dispatch on the type of command if command == '': # post msg.Enqueue(mlist, tolist=1) elif command == 'admin': msg.Enqueue(mlist, toadmin=1) elif command == 'owner': msg.Enqueue(mlist, toowner=1) elif command == 'request': msg.Enqueue(mlist, torequest=1) elif command in ('join', 'leave'): # TBD: this is a hack! if command == 'join': msg['Subject'] = 'subscribe' else: msg['Subject'] = 'unsubscribe' msg.Enqueue(mlist, torequest=1)
Center( CheckBox('discardalldefersp', 0).Format() + ' ' + _('Discard all messages marked <em>Defer</em>'))) # 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':
def main(): global ssort # 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 as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') handle_no_list(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'admindb: 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) try: cgidata.getfirst('adminpw', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) # Send this with a 400 status. print('Status: 400 Bad Request') print(doc.Format()) return # CSRF check safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details'] params = list(cgidata.keys()) if set(params) - set(safe_params): csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token')) else: csrf_checked = True # if password is present, void cookie to force password authentication. if cgidata.getfirst('adminpw'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthListModerator, mm_cfg.AuthSiteAdmin), cgidata.getfirst('adminpw', '')): if 'adminpw' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() remote = os.environ.get('HTTP_FORWARDED_FOR', os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) syslog('security', 'Authorization failed (admindb): list=%s: remote=%s', listname, remote) else: msg = '' Auth.loginpage(mlist, 'admindb', msg=msg) return # Add logout function. Note that admindb may be accessed with # site-wide admin, moderator and list admin privileges. # site admin may have site or admin cookie. (or both?) # See if this is a logout request if len(parts) >= 2 and parts[1] == 'logout': if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site': print(mlist.ZapCookie(mm_cfg.AuthSiteAdmin)) if mlist.AuthContextInfo(mm_cfg.AuthListModerator)[0]: print(mlist.ZapCookie(mm_cfg.AuthListModerator)) print(mlist.ZapCookie(mm_cfg.AuthListAdmin)) Auth.loginpage(mlist, 'admindb', frontpage=1) 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 isinstance(qs, list): sender = qs[0] qs = cgi.parse_qs(envar).get('msgid') if qs and isinstance(qs,list): msgid = qs[0] qs = cgi.parse_qs(envar).get('details') if qs and isinstance(qs, list): 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 list(cgidata.keys()) or 'admlogin' in cgidata: # If this is not a form submission (i.e. there are no keys in the # form) or it's a login, 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')) if csrf_checked: process_form(mlist, doc, cgidata) else: doc.addError( _('The form lifetime has expired. (request forgery check)')) # Now print the results and we're done. Short circuit for when there # are no pending requests, but be sure to save the results! admindburl = mlist.GetScriptURL('admindb', absolute=1) if not mlist.NumRequestsPending(): title = _('%(realname)s Administrative Database') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.AddItem(_('There are no pending requests.')) doc.AddItem(' ') doc.AddItem(Link(admindburl, _('Click here to reload this page.'))) # Put 'Logout' link before the footer doc.AddItem('\n<div align="right"><font size="+2">') doc.AddItem(Link('%s/logout' % admindburl, '<b>%s</b>' % _('Logout'))) doc.AddItem('</font></div>\n') doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) mlist.Save() return form = Form(admindburl, mlist=mlist, contexts=AUTH_CONTEXTS) # Add the instructions template if details == 'instructions': doc.AddItem(Header( 2, _('Detailed instructions for the administrative database'))) else: doc.AddItem(Header( 2, _('Administrative requests for mailing list:') + ' <em>%s</em>' % mlist.real_name)) if details != 'instructions': form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) nomessages = not mlist.GetHeldMessageIds() if not (details or sender or msgid or nomessages): form.AddItem(Center( '<label>' + CheckBox('discardalldefersp', 0).Format() + ' ' + _('Discard all messages marked <em>Defer</em>') + '</label>' )) # 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, ssort) addform = num > 0 # Finish up the document, adding buttons to the form if addform: doc.AddItem(form) form.AddItem('<hr>') if not (details or sender or msgid or nomessages): form.AddItem(Center( '<label>' + CheckBox('discardalldefersp', 0).Format() + ' ' + _('Discard all messages marked <em>Defer</em>') + '</label>' )) form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) # Put 'Logout' link before the footer doc.AddItem('\n<div align="right"><font size="+2">') doc.AddItem(Link('%s/logout' % admindburl, '<b>%s</b>' % _('Logout'))) doc.AddItem('</font></div>\n') doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) # Commit all changes mlist.Save() finally: mlist.Unlock()
def show_helds_overview(mlist, form, ssort=SSENDER): # Sort the held messages. byskey = helds_by_skey(mlist, ssort) if not byskey: return 0 form.AddItem('<hr>') form.AddItem(Center(Header(2, _('Held Messages')))) # Add the sort sequence choices if wanted if mm_cfg.DISPLAY_HELD_SUMMARY_SORT_BUTTONS: form.AddItem(Center(_('Show this list grouped/sorted by'))) form.AddItem(Center(hacky_radio_buttons( 'summary_sort', (_('sender/sender'), _('sender/time'), _('ungrouped/time')), (SSENDER, SSENDERTIME, STIME), (ssort == SSENDER, ssort == SSENDERTIME, ssort == STIME)))) # Add the by-sender overview tables admindburl = mlist.GetScriptURL('admindb', absolute=1) table = Table(border=0) form.AddItem(table) skeys = list(byskey.keys()) skeys.sort() for skey in skeys: sender = skey[1] qsender = quote_plus(sender) esender = Utils.websafe(sender) senderurl = admindburl + '?sender=' + qsender # The encompassing sender table stable = Table(border=1) stable.AddRow([Center(Bold(_('From:')).Format() + esender)]) stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2) left = Table(border=0) 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([ '<label>' + CheckBox('senderpreserve-' + qsender, 1).Format() + ' ' + _('Preserve messages for the site administrator') + '</label>' ]) left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ '<label>' + CheckBox('senderforward-' + qsender, 1).Format() + ' ' + _('Forward messages (individually) to:') + '</label>' ]) left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ TextBox('senderforwardto-' + qsender, value=mlist.GetOwnerEmail()) ]) 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([ '<label>' + CheckBox('senderclearmodp-' + qsender, 1).Format() + ' ' + _("Clear this member's <em>moderate</em> flag") + '</label>' ]) else: left.AddRow( [_('<em>The sender is now a member of this list</em>')]) 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([ '<label>' + CheckBox('senderfilterp-' + qsender, 1).Format() + ' ' + _('Add <b>%(esender)s</b> to one of these sender filters:') + '</label>' ]) 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([ '<label>' + CheckBox('senderbanp-' + qsender, 1).Format() + ' ' + _("""Ban <b>%(esender)s</b> from ever subscribing to this mailing list""") + '</label>']) left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) right = Table(border=0) 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) right.AddRow([' ', ' ']) counter = 1 for ptime, id in byskey[skey]: 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 as 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(border=0) t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), Bold(_('Subject:')), Utils.websafe(dispsubj) ]) t.AddRow([' ', Bold(_('Size:')), str(size) + _(' bytes')]) if reason: reason = _(reason) else: reason = _('not available') t.AddRow([' ', Bold(_('Reason:')), reason]) # Include the date we received the message, if available when = msgdata.get('received_time') if when: t.AddRow([' ', Bold(_('Received:')), time.ctime(when)]) t.AddRow([InputObj(qsender, 'hidden', str(id), False).Format()]) counter += 1 right.AddRow([t]) stable.AddRow([left, right]) table.AddRow([stable]) return 1
def show_post_requests(mlist, id, info, total, count, form): # Mailman.ListAdmin.__handlepost no longer tests for pre 2.0beta3 ptime, sender, subject, reason, filename, msgdata = info form.AddItem('<hr>') # 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(Center(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 as e: if e.errno != errno.ENOENT: raise form.AddItem(_('<em>Message with id #%(id)d was lost.')) form.AddItem('<p>') # 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(_('<em>Message with id #%(id)d is corrupted.')) # BAW: Should we really delete this, or shuttle it off for site admin # to look more closely at? form.AddItem('<p>') # 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, decode=True): lines.append(line) chars += len(line) if chars >= limit > 0: break # We may have gone over the limit on the last line, but keep the full line # anyway to avoid losing part of a multibyte character. body = EMPTYSTRING.join(lines) # Get message charset and try encode in list charset # We get it from the first text part. # We need to replace invalid characters here or we can throw an uncaught # exception in doc.Format(). for part in msg.walk(): if part.get_content_maintype() == 'text': # Watchout for charset= with no value. mcset = part.get_content_charset() or 'us-ascii' break else: mcset = 'us-ascii' lcset = Utils.GetCharSet(mlist.preferred_language) if mcset != lcset: try: body = str(body, mcset, 'replace') except (LookupError, UnicodeError, ValueError): pass hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in list(msg.items())]) hdrtxt = Utils.websafe(hdrtxt) # Okay, we've reconstituted the message just fine. Now for the fun part! t = Table(cellspacing=0, cellpadding=0, width='100%') t.AddRow([Bold(_('From:')), sender]) row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() t.AddCellInfo(row, col-1, align='right') t.AddRow([Bold(_('Subject:')), Utils.websafe(Utils.oneline(subject, lcset))]) t.AddCellInfo(row+1, col-1, align='right') t.AddRow([Bold(_('Reason:')), _(reason)]) t.AddCellInfo(row+2, col-1, align='right') when = msgdata.get('received_time') if when: t.AddRow([Bold(_('Received:')), time.ctime(when)]) t.AddCellInfo(row+3, col-1, align='right') buttons = hacky_radio_buttons(id, (_('Defer'), _('Approve'), _('Reject'), _('Discard')), (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), (1, 0, 0, 0), spacing=5) t.AddRow([Bold(_('Action:')), buttons]) t.AddCellInfo(t.GetCurrentRowIndex(), col-1, align='right') t.AddRow([' ', '<label>' + CheckBox('preserve-%d' % id, 'on', 0).Format() + ' ' + _('Preserve message for site administrator') + '</label>' ]) t.AddRow([' ', '<label>' + CheckBox('forward-%d' % id, 'on', 0).Format() + ' ' + _('Additionally, forward this message to: ') + '</label>' + TextBox('forward-addr-%d' % id, size=47, value=mlist.GetOwnerEmail()).Format() ]) notice = msgdata.get('rejection_notice', _('[No explanation given]')) t.AddRow([ Bold(_('If you reject this post,<br>please explain (optional):')), TextArea('comment-%d' % id, rows=4, cols=EXCERPT_WIDTH, text = Utils.wrap(_(notice), column=80)) ]) row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() t.AddCellInfo(row, col-1, align='right') t.AddRow([Bold(_('Message Headers:')), TextArea('headers-%d' % id, hdrtxt, rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)]) row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() t.AddCellInfo(row, col-1, align='right') t.AddRow([Bold(_('Message Excerpt:')), TextArea('fulltext-%d' % id, Utils.websafe(body), rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)]) t.AddCellInfo(row+1, col-1, align='right') form.AddItem(t) form.AddItem('<p>')
def process_form(mlist, doc, cgidata): global ssort senderactions = {} badaddrs = [] # Sender-centric actions for k in list(cgidata.keys()): for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-', 'senderforwardto-', 'senderfilterp-', 'senderfilter-', 'senderclearmodp-', 'senderbanp-'): if k.startswith(prefix): action = k[:len(prefix)-1] qsender = k[len(prefix):] sender = unquote_plus(qsender) value = cgidata.getfirst(k) senderactions.setdefault(sender, {})[action] = value for id in cgidata.getlist(qsender): senderactions[sender].setdefault('message_ids', []).append(int(id)) # discard-all-defers try: discardalldefersp = cgidata.getfirst('discardalldefersp', 0) except ValueError: discardalldefersp = 0 # Get the summary sequence ssort = int(cgidata.getfirst('summary_sort', SSENDER)) for sender in list(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', '') byskey = helds_by_skey(mlist, SSENDER) for ptime, id in byskey.get((0, sender), []): if id not in senderactions[sender]['message_ids']: # It arrived after the page was displayed. Skip it. continue try: msgdata = mlist.GetRecord(id)[5] comment = msgdata.get('rejection_notice', _('[No explanation given]')) 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): # Check for an invalid sender address. try: Utils.ValidateEmail(sender) except Errors.EmailAddressError: # Don't check for dups. Report it once for each checked box. badaddrs.append(sender) else: 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): # Check for an invalid sender address. try: Utils.ValidateEmail(sender) except Errors.EmailAddressError: # Don't check for dups. Report it once for each checked box. badaddrs.append(sender) else: if sender not in mlist.ban_list: mlist.ban_list.append(sender) # Now, do message specific actions banaddrs = [] erroraddrs = [] for k in list(cgidata.keys()): formv = cgidata[k] if isinstance(formv, list): 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 try: if mlist.GetRecordType(request_id) == HELDMSG: msgdata = mlist.GetRecord(request_id)[5] comment = msgdata.get('rejection_notice', _('[No explanation given]')) else: comment = _('[No explanation given]') except KeyError: # Someone else must have handled this one after we got the page. continue preserve = 0 forward = 0 forwardaddr = '' if commentkey in cgidata: comment = cgidata[commentkey].value if preservekey in cgidata: preserve = cgidata[preservekey].value if forwardkey in cgidata: forward = cgidata[forwardkey].value if forwardaddrkey in cgidata: 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.getfirst(bankey): sender = mlist.GetRecord(request_id)[1] if sender not in mlist.ban_list: # We don't need to validate the sender. An invalid address # can't get here. 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 as v: erroraddrs.append(v) except Errors.MembershipIsBanned as pattern: sender = mlist.GetRecord(request_id)[1] banaddrs.append((sender, pattern)) # save the list and print the results doc.AddItem(Header(2, _('Database Updated...'))) if erroraddrs: for addr in erroraddrs: addr = Utils.websafe(addr) doc.AddItem(repr(addr) + _(' is already a member') + '<br>') if banaddrs: for addr, patt in banaddrs: addr = Utils.websafe(addr) doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '<br>') if badaddrs: for addr in badaddrs: addr = Utils.websafe(addr) doc.AddItem(repr(addr) + ': ' + _('Bad/Invalid email address') + '<br>')
def Authenticate(self, authcontexts, response, user=None): # Given a list of authentication contexts, check to see if the # response matches one of the passwords. authcontexts must be a # sequence, and if it contains the context AuthUser, then the user # argument must not be None. # # Return the authcontext from the argument sequence that matches the # response, or UnAuthorized. for ac in authcontexts: if ac == mm_cfg.AuthCreator: ok = Utils.check_global_password(response, siteadmin=0) if ok: return mm_cfg.AuthCreator elif ac == mm_cfg.AuthSiteAdmin: ok = Utils.check_global_password(response) if ok: return mm_cfg.AuthSiteAdmin elif ac == mm_cfg.AuthListAdmin: def cryptmatchp(response, secret): try: salt = secret[:2] if crypt and crypt.crypt(response, salt) == secret: return True return False except TypeError: # BAW: Hard to say why we can get a TypeError here. # SF bug report #585776 says crypt.crypt() can raise # this if salt contains null bytes, although I don't # know how that can happen (perhaps if a MM2.0 list # with USE_CRYPT = 0 has been updated? Doubtful. return False # The password for the list admin and list moderator are not # kept as plain text, but instead as an sha hexdigest. The # response being passed in is plain text, so we need to # digestify it first. Note however, that for backwards # compatibility reasons, we'll also check the admin response # against the crypted and md5'd passwords, and if they match, # we'll auto-migrate the passwords to sha. key, secret = self.AuthContextInfo(ac) if secret is None: continue sharesponse = sha_new(response).hexdigest() upgrade = ok = False if sharesponse == secret: ok = True elif md5_new(response).digest() == secret: ok = upgrade = True elif cryptmatchp(response, secret): ok = upgrade = True if upgrade: save_and_unlock = False if not self.__mlist.Locked(): self.__mlist.Lock() save_and_unlock = True try: self.__mlist.password = sharesponse if save_and_unlock: self.__mlist.Save() finally: if save_and_unlock: self.__mlist.Unlock() if ok: return ac elif ac == mm_cfg.AuthListModerator: # The list moderator password must be sha'd key, secret = self.AuthContextInfo(ac) if secret and sha_new(response).hexdigest() == secret: return ac elif ac == mm_cfg.AuthUser: if user is not None: try: if self.__mlist.authenticateMember(user, response): return ac except Errors.NotAMemberError: pass else: # What is this context??? syslog('error', 'Bad authcontext: %s', ac) raise ValueError, 'Bad authcontext: %s' % ac return mm_cfg.UnAuthorized
# messages in form should go in selected language (if any...) try: lang = cgidata.getfirst('language') except TypeError: # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) # Send this with a 400 status. print 'Status: 400 Bad Request' print doc.Format() return 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). password = cgidata.getfirst('roster-pw', '').strip() addr = cgidata.getfirst('roster-email', '').strip() list_hidden = (not mlist.WebAuthenticate((mm_cfg.AuthUser,), password, addr) and mlist.WebAuthenticate((mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin),
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, 'listinfo': self.GetScriptURL('listinfo', absolute=1), }, mlist=self))
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.getfirst(boxtag) pattern = cgidata.getfirst(reboxtag) desc = cgidata.getfirst(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: orpattern = OR.join(pattern.splitlines()) re.compile(orpattern) except (re.error, TypeError): safepattern = Utils.websafe(orpattern) 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.getfirst(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.getfirst('topics_enabled', mlist.topics_enabled)) except ValueError: # BAW: should really print a warning pass try: mlist.topics_bodylines_limit = int( cgidata.getfirst('topics_bodylines_limit', mlist.topics_bodylines_limit)) except ValueError: # BAW: should really print a warning pass