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 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 Format(self, indent=0): # If I don't start a new I ignore indent output = '<%s>%s</%s>' % ( self.tag, Utils.websafe(Container.Format(self, indent)), self.tag) return output
def scrub_html1(self, part): # sanitize == 1 payload = Utils.websafe(part.get_payload(decode=True)) # For whitespace in the margin, change spaces into # non-breaking spaces, and tabs into 8 of those. Then use a # mono-space font. Still looks hideous to me, but then I'd # just as soon discard them. def doreplace(s): return s.expandtabs(8).replace(' ', ' ') lines = [doreplace(s) for s in payload.split('\n')] payload = '<tt>\n' + BR.join(lines) + '\n</tt>\n' part.set_payload(payload) # We're replacing the payload with the decoded payload so this # will just get in the way. del part['content-transfer-encoding'] omask = os.umask(002) try: url = save_attachment(self.mlist, part, self.dir, filter_html=False) finally: os.umask(omask) self.msgtexts.append(unicode(_("""\ An HTML attachment was scrubbed... URL: %(url)s """), self.lcset))
def FormatBox(self, name, size=20, value=''): if isinstance(value, str): safevalue = Utils.websafe(value) else: safevalue = value return '<INPUT type="Text" name="%s" size="%d" value="%s">' % ( name, size, safevalue)
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 GetStandardReplacements(self, lang=None): dmember_len = len(self.getDigestMemberKeys()) member_len = len(self.getRegularMemberKeys()) # If only one language is enabled for this mailing list, omit the # language choice buttons. if len(self.GetAvailableLanguages()) == 1: listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) else: listlangs = self.GetLangSelectBox(lang).Format() d = { '<mm-mailman-footer>' : self.GetMailmanFooter(), '<mm-list-name>' : self.real_name, '<mm-email-user>' : self._internal_name, '<mm-list-description>' : Utils.websafe(self.description), '<mm-list-info>' : '<!---->' + BR.join(self.info.split(NL)) + '<!---->', '<mm-form-end>' : self.FormatFormEnd(), '<mm-archive>' : self.FormatArchiveAnchor(), '</mm-archive>' : '</a>', '<mm-list-subscription-msg>' : self.FormatSubscriptionMsg(), '<mm-restricted-list-message>' : \ self.RestrictedListMessage(_('The current archive'), self.archive_private), '<mm-num-reg-users>' : `member_len`, '<mm-num-digesters>' : `dmember_len`, '<mm-num-members>' : (`member_len + dmember_len`), '<mm-posting-addr>' : '%s' % self.GetListEmail(), '<mm-request-addr>' : '%s' % self.GetRequestEmail(), '<mm-owner>' : self.GetOwnerEmail(), '<mm-reminder>' : self.FormatReminder(self.preferred_language), '<mm-host>' : self.host_name, '<mm-list-langs>' : listlangs, }
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() lenparts = parts and len(parts) if not parts or lenparts < 1: title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_('Invalid options to CGI script.')) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() return # get the list and user's name listname = parts[0].lower() # open list try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_('No such list <em>%(safelistname)s</em>')) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'No such list "%s": %s\n', listname, e) return
def FormatHidden(name, value='display'): if isinstance(value, str): safevalue = Utils.websafe(value) else: safevalue = value return '<INPUT type="Hidden" name="%s" value="%s">' % ( name, safevalue)
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() lenparts = parts and len(parts) if not parts or lenparts < 1: title = _("CGI script error") doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_("Invalid options to CGI script.")) doc.AddItem("<hr>") doc.AddItem(MailmanLogo()) print doc.Format() return # get the list and user's name listname = parts[0].lower() # open list try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _("CGI script error") doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_("No such list <em>%(safelistname)s</em>")) doc.AddItem("<hr>") doc.AddItem(MailmanLogo()) # Send this with a 404 status. print "Status: 404 Not Found" print doc.Format() syslog("error", 'No such list "%s": %s\n', listname, e) return
def display(mlist, captchas): """Returns a CAPTCHA question, the HTML for the answer box, and the data to be put into the CSRF token""" idx = random.randrange(len(captchas)) question = captchas[idx][0] box_html = mlist.FormatBox('captcha_answer', size=30) return (Utils.websafe(question), box_html, str(idx))
def FormatBox(self, name, size=20, value=''): if isinstance(value, str): safevalue = Utils.websafe(value) else: safevalue = value return '<INPUT type="Text" name="%s" size="%d" value="%s">' % ( name, size, safevalue)
def GetStandardReplacements(self, lang=None): dmember_len = len(self.getDigestMemberKeys()) member_len = len(self.getRegularMemberKeys()) # If only one language is enabled for this mailing list, omit the # language choice buttons. if len(self.GetAvailableLanguages()) == 1: listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) else: listlangs = self.GetLangSelectBox(lang).Format() d = { '<mm-mailman-footer>' : self.GetMailmanFooter(), '<mm-list-name>' : self.real_name, '<mm-email-user>' : self._internal_name, '<mm-list-description>' : Utils.websafe(self.description), '<mm-list-info>' : '<!---->' + BR.join(self.info.split(NL)) + '<!---->', '<mm-form-end>' : self.FormatFormEnd(), '<mm-archive>' : self.FormatArchiveAnchor(), '</mm-archive>' : '</a>', '<mm-list-subscription-msg>' : self.FormatSubscriptionMsg(), '<mm-restricted-list-message>' : \ self.RestrictedListMessage(_('The current archive'), self.archive_private), '<mm-num-reg-users>' : `member_len`, '<mm-num-digesters>' : `dmember_len`, '<mm-num-members>' : (`member_len + dmember_len`), '<mm-posting-addr>' : '%s' % self.GetListEmail(), '<mm-request-addr>' : '%s' % self.GetRequestEmail(), '<mm-owner>' : self.GetOwnerEmail(), '<mm-reminder>' : self.FormatReminder(self.preferred_language), '<mm-host>' : self.host_name, '<mm-list-langs>' : listlangs, }
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 main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) cgidata = cgi.FieldStorage() parts = Utils.GetPathPieces() if not parts: # Bad URL specification title = _('Bad URL specification') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() syslog('error', 'Bad URL specification: %s', parts) return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() syslog('error', 'No such list "%s": %s\n', listname, e) return
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.SetTitle(_("Private Archive Error")) doc.AddItem(Header(3, _("You must specify a list."))) print doc.Format() return path = os.environ.get('PATH_INFO') tpath = true_path(path) if tpath <> path[1:]: msg = _('Private archive - "./" and "../" not allowed in URL.') doc.SetTitle(msg) doc.AddItem(Header(2, msg)) print doc.Format() syslog('mischief', 'Private archive hostile path: %s', path) return # BAW: This needs to be converted to the Site module abstraction true_filename = os.path.join( mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) listname = parts[0].lower() mboxfile = '' if len(parts) > 1: mboxfile = parts[1] # See if it's the list's mbox file is being requested if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \ listname[:-5] == mboxfile[:-5]: listname = listname[:-5] else: mboxfile = '' # If it's a directory, we have to append index.html in this script. We # must also check for a gzipped file, because the text archives are # usually stored in compressed form. if os.path.isdir(true_filename): true_filename = true_filename + '/index.html' if not os.path.exists(true_filename) and \ os.path.exists(true_filename + '.gz'): true_filename = true_filename + '.gz' try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) msg = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(_("Private Archive Error - %(msg)s")) doc.AddItem(Header(2, msg)) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'private: No such list "%s": %s\n', listname, e) return
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.SetTitle(_("Private Archive Error")) doc.AddItem(Header(3, _("You must specify a list."))) print doc.Format() return path = os.environ.get('PATH_INFO') tpath = true_path(path) if tpath <> path[1:]: msg = _('Private archive - "./" and "../" not allowed in URL.') doc.SetTitle(msg) doc.AddItem(Header(2, msg)) print doc.Format() syslog('mischief', 'Private archive hostile path: %s', path) return # BAW: This needs to be converted to the Site module abstraction true_filename = os.path.join( mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) listname = parts[0].lower() mboxfile = '' if len(parts) > 1: mboxfile = parts[1] # See if it's the list's mbox file is being requested if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \ listname[:-5] == mboxfile[:-5]: listname = listname[:-5] else: mboxfile = '' # If it's a directory, we have to append index.html in this script. We # must also check for a gzipped file, because the text archives are # usually stored in compressed form. if os.path.isdir(true_filename): true_filename = true_filename + '/index.html' if not os.path.exists(true_filename) and \ os.path.exists(true_filename + '.gz'): true_filename = true_filename + '.gz' try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) msg = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(_("Private Archive Error - %(msg)s")) doc.AddItem(Header(2, msg)) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'private: No such list "%s": %s\n', listname, e) return
def request_create (self): """Creates a list (name taken from the CGI form value called lc_name). Returns None if the list was created successfully. Returns a string containing error message if list could not be created for whatever reason.""" self._ml = MailList.MailList() err = self.errcheck(action='create') if err: return err # We've got all the data we need, so go ahead and try to create the # list See admin.py for why we need to set up the signal handler. try: signal.signal(signal.SIGTERM, self.sigterm_handler) pwhex = sha_new(self.pw).hexdigest() # Guarantee that all newly created files have the proper permission. # proper group ownership should be assured by the autoconf script # enforcing that all directories have the group sticky bit set oldmask = os.umask(002) try: try: self.ml.Create(self.ln, self.owner[0], pwhex, self.langs, self.eh, urlhost=self.hn) finally: os.umask(oldmask) except Errors.EmailAddressError, e: if e.args: s = Utils.websafe(e.args[0]) else: s = Utils.websafe(owner) return 'Bad owner email address: %s' % s except Errors.MMListAlreadyExistsError: return 'List already exists: %s' % self.ln except Errors.BadListNameError, e: if e.args: s = Utils.websafe(e.args[0]) else: s = Utils.websafe(listname) return 'Illegal list name: %s' % self.ln
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/time'))), 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. # The list ws returned sorted ascending. Keep the last. for id in ids[:-1]: mlist.HandleRequest(id, mm_cfg.DISCARD) id = ids[-1] stime, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) fullname = Utils.uncanonstr(fullname, mlist.preferred_language) displaytime = time.ctime(stime) 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><br>%s' % (paddr, Utils.websafe(fullname), displaytime), radio, TextBox('comment-%d' % id, size=40) ]) num += 1 if num > 0: form.AddItem(table) return num
def __init__(self, name, text='', rows=None, cols=None, wrap='soft', readonly=0): if isinstance(text, str): safetext = Utils.websafe(text) else: safetext = text self.name = name self.text = safetext self.rows = rows self.cols = cols self.wrap = wrap self.readonly = readonly
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 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 main(): # Trick out pygettext since we want to mark template_data as translatable, # but we don't want to actually translate it here. def _(s): return s template_data = ( ('listinfo.html', _('General list information page')), ('subscribe.html', _('Subscribe results page')), ('options.html', _('User specific options page')), ('subscribeack.txt', _('Welcome email text file')), ('masthead.txt', _('Digest masthead')), ('postheld.txt', _('User notice of held post')), ('approve.txt', _('User notice of held subscription')), ('refuse.txt', _('Notice of post refused by moderator')), ('invite.txt', _('Invitation to join list')), ('verify.txt', _('Request to confirm subscription')), ('unsub.txt', _('Request to confirm unsubscription')), ('nomoretoday.txt', _('User notice of autoresponse limit')), ('postack.txt', _('User post acknowledgement')), ('disabled.txt', _('Subscription disabled by bounce warning')), ('admlogin.html', _('Admin/moderator login page')), ('private.html', _('Private archive login page')), ('userpass.txt', _('On demand password reminder')), ) _ = i18n._ doc = Document() # Set up the system default language i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.AddItem(Header(2, _("List name is required."))) print doc.Format() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>'))) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'edithtml: No such list "%s": %s', listname, e) return
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.items() addrs.sort() num = 0 for addr, ids in addrs: # Eliminate duplicates # Here the order doesn't matter as the data is just the address. 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 table.AddRow(['%s<br><em>%s</em>' % (addr, 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 main(): parts = Utils.GetPathPieces() if not parts: listinfo_overview() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) listinfo_overview(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'No such list "%s": %s', listname, e) return
def main(): parts = Utils.GetPathPieces() if not parts: error_page(_('Invalid options to CGI script')) return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) error_page(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'roster: no such list "%s": %s', listname, e) return
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 main(): # 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, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) handle_no_list(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'No such list "%s": %s\n', listname, e) return
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) cgidata = cgi.FieldStorage() try: cgidata.getfirst('password', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. 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 parts = Utils.GetPathPieces() if not parts: # Bad URL specification title = _('Bad URL specification') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() syslog('error', 'Bad URL specification: %s', parts) return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(_('No such list %(safelistname)s')) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'rmlist: No such list "%s": %s\n', listname, e) return
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) method = Utils.GetRequestMethod() if method.lower() not in ('get', 'post'): title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_('Invalid request method: %(method)s')) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print 'Status: 405 Method Not Allowed' print doc.Format() return parts = Utils.GetPathPieces() lenparts = parts and len(parts) if not parts or lenparts < 1: title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_('Invalid options to CGI script.')) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() return # get the list and user's name listname = parts[0].lower() # open list try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_('No such list <em>%(safelistname)s</em>')) doc.AddItem('<hr>') 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
def main(): parts = Utils.GetPathPieces() if not parts: listinfo_overview() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) # Send this with a 404 status. print 'Status: 404 Not Found' listinfo_overview(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'No such list "%s": %s', listname, e) return
def __init__(self, name, text='', rows=None, cols=None, wrap='soft', readonly=0): if isinstance(text, str): safetext = Utils.websafe(text) else: safetext = text self.name = name self.text = safetext self.rows = rows self.cols = cols self.wrap = wrap self.readonly = readonly
def main(): # 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, 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', 'No such list "%s": %s\n', listname, e) return
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, 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
def FormatUsers(self, digest, lang=None, list_hidden=False): if lang is None: lang = self.preferred_language conceal_sub = mm_cfg.ConcealSubscription people = [] if digest: members = self.getDigestMemberKeys() else: members = self.getRegularMemberKeys() for m in members: if list_hidden or not self.getMemberOption(m, conceal_sub): people.append(m) num_concealed = len(members) - len(people) if num_concealed == 1: concealed = _('<em>(1 private member not shown)</em>') elif num_concealed > 1: concealed = _( '<em>(%(num_concealed)d private members not shown)</em>') else: concealed = '' items = [] people.sort() obscure = self.obscure_addresses for person in people: id = Utils.ObscureEmail(person) url = self.GetOptionsURL(person, obscure=obscure) person = self.getMemberCPAddress(person) if obscure: showing = Utils.ObscureEmail(person, for_text=1) else: showing = person realname = Utils.uncanonstr(self.getMemberName(person), lang) if realname and mm_cfg.ROSTER_DISPLAY_REALNAME: showing += " (%s)" % Utils.websafe(realname) got = Link(url, showing) if self.getDeliveryStatus(person) <> MemberAdaptor.ENABLED: got = Italic('(', got, ')') items.append(got) # Just return the .Format() so this works until I finish # converting everything to htmlformat... return concealed + UnorderedList(*tuple(items)).Format()
def FormatUsers(self, digest, lang=None, list_hidden=False): if lang is None: lang = self.preferred_language conceal_sub = mm_cfg.ConcealSubscription people = [] if digest: members = self.getDigestMemberKeys() else: members = self.getRegularMemberKeys() for m in members: if list_hidden or not self.getMemberOption(m, conceal_sub): people.append(m) num_concealed = len(members) - len(people) if num_concealed == 1: concealed = _('<em>(1 private member not shown)</em>') elif num_concealed > 1: concealed = _( '<em>(%(num_concealed)d private members not shown)</em>') else: concealed = '' items = [] people.sort() obscure = self.obscure_addresses for person in people: id = Utils.ObscureEmail(person) url = self.GetOptionsURL(person, obscure=obscure) if obscure: showing = Utils.ObscureEmail(person, for_text=1) else: showing = person realname = Utils.uncanonstr(self.getMemberName(person), lang) if realname and mm_cfg.ROSTER_DISPLAY_REALNAME: showing += " (%s)" % Utils.websafe(realname) got = Link(url, showing) if self.getDeliveryStatus(person) <> MemberAdaptor.ENABLED: got = Italic('(', got, ')') items.append(got) # Just return the .Format() so this works until I finish # converting everything to htmlformat... return concealed + UnorderedList(*tuple(items)).Format()
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script'))) print doc.Format() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('No such list <em>%(safelistname)s</em>'))) print doc.Format() syslog('error', 'No such list "%s": %s\n', listname, e) return
def main(): # Trick out pygettext since we want to mark template_data as translatable, # but we don't want to actually translate it here. def _(s): return s template_data = ( ('listinfo.html', _('General list information page')), ('subscribe.html', _('Subscribe results page')), ('options.html', _('User specific options page')), ('subscribeack.txt', _('Welcome email text file')), ('masthead.txt', _('Digest masthead')), ) _ = i18n._ doc = Document() # Set up the system default language i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.AddItem(Header(2, _("List name is required."))) print doc.Format() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>'))) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'No such list "%s": %s', listname, e) return
def main(): # print 'Content-type: text/plain\n\n' print 'Content-type: text/plain; charset=us-ascii\n' doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.SetTitle(_("Private Archive Error")) doc.AddItem(Header(3, _("You must specify a list."))) print doc.Format() return path = os.environ.get('PATH_INFO') tpath = true_path(path) if tpath <> path[1:]: msg = _('Private archive - "./" and "../" not allowed in URL.') doc.SetTitle(msg) doc.AddItem(Header(2, msg)) print doc.Format() syslog('mischief', 'Private archive hostile path: %s', path) return # BAW: This needs to be converted to the Site module abstraction true_filename = os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) msg = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(_("Private Archive Error - %(msg)s")) doc.AddItem(Header(2, msg)) print doc.Format() syslog('error', 'No such list "%s": %s\n', listname, e) return
def handleForm(self, mlist, category, subcat, cgidata, doc): for item in self.GetConfigInfo(mlist, category, subcat): # Skip descriptions and legacy non-attributes if not isinstance(item, tuple) or len(item) < 5: continue # Unpack the gui item description property, wtype, args, deps, desc = item[0:5] # BAW: I know this code is a little crufty but I wanted to # reproduce the semantics of the original code in admin.py as # closely as possible, for now. We can clean it up later. # # The property may be uploadable... uploadprop = property + '_upload' if uploadprop in cgidata and cgidata[uploadprop].value: val = cgidata[uploadprop].value elif property not in cgidata: continue elif isinstance(cgidata[property], list): val = [x.value for x in cgidata[property]] else: val = cgidata[property].value # Coerce the value to the expected type, raising exceptions if the # value is invalid. try: val = self._getValidValue(mlist, property, wtype, val) except ValueError: doc.addError(_('Invalid value for variable: %(property)s')) # This is the parent of MMBadEmailError and MMHostileAddress except Errors.EmailAddressError as error: error = Utils.websafe(str(error)) doc.addError( _('Bad email address for option %(property)s: %(error)s')) else: # Set the attribute, which will normally delegate to the mlist self._setValue(mlist, property, val, doc) # Do a final sweep once all the attributes have been set. This is how # we can do cross-attribute assertions self._postValidate(mlist, doc)
def main(): parts = Utils.GetPathPieces() if not parts: listinfo_overview() 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') listinfo_overview(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'listinfo: No such list "%s": %s', listname, e) return # See if the user want to see this page in other language cgidata = cgi.FieldStorage() try: language = 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(language): language = mlist.preferred_language i18n.set_language(language) list_listinfo(mlist, language)
def handler (self, parts): owned = [] cu = self.curr_user for mln, ml in self.all_mls.iteritems(): if cu in ml.owner or cu in mm_cfg.SSO_ADMIN_AUTHIDS: owned.append({'real_name' : ml.real_name, 'description' : Utils.websafe(ml.description), 'advertised' : ml.advertised, }) self.kwargs_add('lists', owned) self.kwargs_add('can_create_lists', can_create_lists) if self.cgidata.has_key('lc_submit'): if parts[0] == 'new': self.handler_new(parts, submit=True) else: self.handler_edit(parts) else: ## This is the case when we are landing here through a GET ## request. self.kwargs_add('action_taken', False) if (len(parts) == 0 or parts[0] == ''): ## This is the root /create page self.kwargs_add('list_to_edit', None) self.kwargs_add('lc_empty_form', False) elif parts[0] == 'new': ## This is the root /create/new page self.handler_new(parts, submit=False) else: ## This is some /create/xyz type page which is for editing the ## configuration of list named xyz self.kwargs_add('list_to_edit', self.all_mls[parts[0].lower()]) self.kwargs_add('lc_empty_form', False) self.render()
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) cgidata = cgi.FieldStorage() parts = Utils.GetPathPieces() if not parts: # Bad URL specification title = _('Bad URL specification') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() syslog('error', 'Bad URL specification: %s', parts) return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'No such list "%s": %s\n', listname, e) return
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script'))) print doc.Format() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('No such list <em>%(safelistname)s</em>'))) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'subscribe: No such list "%s": %s\n', listname, e) return
def _handleForm(self, mlist, category, subcat, cgidata, doc): # TK: If there is no hdrfilter_* in cgidata, we should not touch # the header filter rules. if not cgidata.has_key('hdrfilter_rebox_01'): return # First deal with rules = [] # We start i at 1 and keep going until we no longer find items keyed # with the marked tags. i = 1 downi = None while True: deltag = 'hdrfilter_delete_%02d' % i reboxtag = 'hdrfilter_rebox_%02d' % i actiontag = 'hdrfilter_action_%02d' % i wheretag = 'hdrfilter_where_%02d' % i addtag = 'hdrfilter_add_%02d' % i newtag = 'hdrfilter_new_%02d' % i uptag = 'hdrfilter_up_%02d' % i downtag = 'hdrfilter_down_%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 pattern = cgidata.getfirst(reboxtag) try: action = int(cgidata.getfirst(actiontag)) # We'll get a TypeError when the actiontag is missing and the # .getvalue() call returns None. except (ValueError, TypeError): action = mm_cfg.DEFER if pattern is None: # We came to the end of the boxes break if cgidata.has_key(newtag) and not pattern: # This new entry is incomplete. if i == 2: # OK it is the first. continue doc.addError( _("""Header filter rules require a pattern. Incomplete filter rules will be ignored.""")) continue # Make sure the pattern was a legal regular expression. # Convert it to unicode if necessary. mo = re.match('.*charset=([-_a-z0-9]+)', os.environ.get('CONTENT_TYPE', ''), re.IGNORECASE) if mo: cset = mo.group(1) else: cset = Utils.GetCharSet(mlist.preferred_language) try: upattern = Utils.xml_to_unicode(pattern, cset) re.compile(upattern) pattern = upattern except (re.error, TypeError): safepattern = Utils.websafe(pattern) doc.addError( _("""The header filter rule pattern '%(safepattern)s' is not a legal regular expression. This rule will be ignored.""")) 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 rule box before the current one rules.append(('', mm_cfg.DEFER, True)) rules.append((pattern, action, False)) # Default is to add it after... else: rules.append((pattern, action, False)) rules.append(('', mm_cfg.DEFER, True)) # Was this an up movement? elif cgidata.has_key(uptag): # As long as this one isn't the first rule, move it up if rules: rules.insert(-1, (pattern, action, False)) else: rules.append((pattern, action, False)) # Was this the down movement? elif cgidata.has_key(downtag): downi = i - 2 rules.append((pattern, action, False)) # Otherwise, just retain this one in the list else: rules.append((pattern, action, False)) # Move any down button filter rule if downi is not None: rule = rules[downi] del rules[downi] rules.insert(downi + 1, rule) mlist.header_filter_rules = rules
def CGIescape(arg, lang=None): if isinstance(arg, types.UnicodeType): s = Utils.websafe(arg) else: s = Utils.websafe(str(arg)) return Utils.uncanonstr(s.replace('"', '"'), lang)
def show_helds_overview(mlist, form): # Sort the held messages by sender bysender = helds_by_sender(mlist) if not bysender: return 0 form.AddItem('<hr>') form.AddItem(Center(Header(2, _('Held Messages')))) # Add the by-sender overview tables admindburl = mlist.GetScriptURL('admindb', absolute=1) table = Table(border=0) form.AddItem(table) senders = bysender.keys() senders.sort() for sender in senders: 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([ CheckBox('senderpreserve-' + qsender, 1).Format() + ' ' + _('Preserve messages for the site administrator') ]) left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ CheckBox('senderforward-' + qsender, 1).Format() + ' ' + _('Forward messages (individually) to:') ]) 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([ CheckBox('senderclearmodp-' + qsender, 1).Format() + ' ' + _("Clear this member's <em>moderate</em> flag") ]) 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([ CheckBox('senderfilterp-' + qsender, 1).Format() + ' ' + _('Add <b>%(esender)s</b> to one of these sender filters:') ]) 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([ CheckBox('senderbanp-' + qsender, 1).Format() + ' ' + _("""Ban <b>%(esender)s</b> from ever subscribing to this mailing list""")]) 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 id in bysender[sender]: 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, 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])
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(): # Trick out pygettext since we want to mark template_data as translatable, # but we don't want to actually translate it here. def _(s): return s template_data = ( ('listinfo.html', _('General list information page')), ('subscribe.html', _('Subscribe results page')), ('options.html', _('User specific options page')), ('subscribeack.txt', _('Welcome email text file')), ('masthead.txt', _('Digest masthead')), ('postheld.txt', _('User notice of held post')), ('approve.txt', _('User notice of held subscription')), ('refuse.txt', _('Notice of post refused by moderator')), ('invite.txt', _('Invitation to join list')), ('verify.txt', _('Request to confirm subscription')), ('unsub.txt', _('Request to confirm unsubscription')), ('nomoretoday.txt', _('User notice of autoresponse limit')), ('postack.txt', _('User post acknowledgement')), ('disabled.txt', _('Subscription disabled by bounce warning')), ('admlogin.html', _('Admin/moderator login page')), ('private.html', _('Private archive login page')), ('userpass.txt', _('On demand password reminder')), ) _ = i18n._ doc = Document() # Set up the system default language i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.AddItem(Header(2, _("List name is required."))) print(doc.Format()) 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) doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>'))) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) syslog('error', 'edithtml: No such list "%s": %s', listname, e) return # Now that we have a valid list, set the language to its default i18n.set_language(mlist.preferred_language) doc.set_language(mlist.preferred_language) # Must be authenticated to get any farther cgidata = cgi.FieldStorage() try: cgidata.getfirst('adminpw', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. 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 = ['VARHELP', 'adminpw', 'admlogin'] 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 # Editing the html for a list is limited to the list admin and site admin. if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), cgidata.getfirst('adminpw', '')): if 'admlogin' 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 (edithtml): list=%s: remote=%s', listname, remote) else: msg = '' Auth.loginpage(mlist, 'admin', msg=msg) return realname = mlist.real_name if len(parts) > 1: template_name = parts[1] for (template, info) in template_data: if template == template_name: template_info = _(info) doc.SetTitle( _('%(realname)s -- Edit html for %(template_info)s')) break else: # Avoid cross-site scripting attacks safetemplatename = Utils.websafe(template_name) doc.SetTitle(_('Edit HTML : Error')) doc.AddItem(Header(2, _("%(safetemplatename)s: Invalid template"))) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return else: doc.SetTitle(_('%(realname)s -- HTML Page Editing')) doc.AddItem(Header(1, _('%(realname)s -- HTML Page Editing'))) doc.AddItem(Header(2, _('Select page to edit:'))) template_list = UnorderedList() for (template, info) in template_data: l = Link(mlist.GetScriptURL('edithtml') + '/' + template, _(info)) template_list.AddItem(l) doc.AddItem(FontSize("+2", template_list)) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return try: if list(cgidata.keys()): if csrf_checked: ChangeHTML(mlist, cgidata, template_name, doc) else: doc.addError( _('The form lifetime has expired. (request forgery check)') ) FormatHTML(mlist, doc, template_name, template_info) finally: doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format())
def process(mlist, msg, msgdata=None): sanitize = mm_cfg.ARCHIVE_HTML_SANITIZER outer = True in_pipeline = False patches = None sigs = None if msgdata is None: msgdata = {} if msgdata: # msgdata is available if it is in GLOBAL_PIPELINE # ie. not in digest or archiver # check if the list owner want to scrub regular delivery # Disabled - function split, attachments saved in pipeline, # if not mlist.scrub_nondigest: # return in_pipeline = True patches = [] sigs = [] dir = calculate_attachments_dir(mlist, msg, msgdata) charset = None lcset = Utils.GetCharSet(mlist.preferred_language) lcset_out = Charset(lcset).output_charset or lcset # Now walk over all subparts of this message and scrub out various types format = delsp = None for part in msg.walk(): ctype = part.get_content_type() # If the part is text/plain, we leave it alone if ctype == "text/plain": # We need to choose a charset for the scrubbed message, so we'll # arbitrarily pick the charset of the first text/plain part in the # message. # MAS: Also get the RFC 3676 stuff from this part. This seems to # work OK for scrub_nondigest. It will also work as far as # scrubbing messages for the archive is concerned, but pipermail # doesn't pay any attention to the RFC 3676 parameters. The plain # format digest is going to be a disaster in any case as some of # messages will be format="flowed" and some not. ToDigest creates # its own Content-Type: header for the plain digest which won't # have RFC 3676 parameters. If the message Content-Type: headers # are retained for display in the digest, the parameters will be # there for information, but not for the MUA. This is the best we # can do without having get_payload() process the parameters. if charset is None: charset = part.get_content_charset(lcset) format = part.get_param("format") delsp = part.get_param("delsp") # TK: if part is attached then check charset and scrub if none if part.get("content-disposition") and not part.get_content_charset(): if in_pipeline: omask = os.umask(002) try: url = save_attachment(mlist, part, dir, patches=patches, sigs=sigs) part[mm_cfg.SCRUBBER_URL_HEADER] = url finally: os.umask(omask) else: url = "<" + part.get(mm_cfg.SCRUBBER_URL_HEADER, "N/A") + ">" filename = part.get_filename(_("not available")) filename = Utils.oneline(filename, lcset) replace_payload_by_text( part, _( """\ An embedded and charset-unspecified text was scrubbed... Name: %(filename)s URL: %(url)s """ ), lcset, ) elif mm_cfg.SCRUBBER_ARCHIVE_ALL_TEXT: if in_pipeline: # clearsigned or attached plaintext that will be shown, still archive the copy omask = os.umask(002) try: url = save_attachment(mlist, part, dir, patches=patches, sigs=sigs) part[mm_cfg.SCRUBBER_URL_HEADER] = url finally: os.umask(omask) elif outer and not msg.is_multipart(): # whole email is only one plaintext. add URL here payload = msg.get_payload(decode=True) del msg["content-type"] del msg["content-transfer-encoding"] payload = "URL: <" + part.get(mm_cfg.SCRUBBER_URL_HEADER, "N/A") + ">\n" + payload msg.set_payload(payload) elif ctype == "text/html" and isinstance(sanitize, IntType): if sanitize == 0: if outer: raise DiscardMessage replace_payload_by_text( part, _("HTML attachment scrubbed and removed"), # Adding charset arg and removing content-type # sets content-type to text/plain lcset, ) elif sanitize == 2: # By leaving it alone, Pipermail will automatically escape it pass elif sanitize == 3: if in_pipeline: # Pull it out as an attachment but leave it unescaped. This # is dangerous, but perhaps useful for heavily moderated # lists. omask = os.umask(002) try: url = save_attachment(mlist, part, dir, filter_html=False, patches=patches, sigs=sigs) part[mm_cfg.SCRUBBER_URL_HEADER] = url finally: os.umask(omask) else: url = "<" + part.get(mm_cfg.SCRUBBER_URL_HEADER, "N/A") + ">" replace_payload_by_text( part, _( """\ An HTML attachment was scrubbed... URL: %(url)s """ ), lcset, ) else: if in_pipeline: # TODO if in_pipeline should preserve original attachment. but no biggie # HTML-escape it and store it as an attachment, but make it # look a /little/ bit prettier. :( payload = Utils.websafe(part.get_payload(decode=True)) # For whitespace in the margin, change spaces into # non-breaking spaces, and tabs into 8 of those. Then use a # mono-space font. Still looks hideous to me, but then I'd # just as soon discard them. def doreplace(s): return s.expandtabs(8).replace(" ", " ") lines = [doreplace(s) for s in payload.split("\n")] payload = "<tt>\n" + BR.join(lines) + "\n</tt>\n" part.set_payload(payload) # We're replacing the payload with the decoded payload so this # will just get in the way. del part["content-transfer-encoding"] omask = os.umask(002) try: url = save_attachment(mlist, part, dir, filter_html=False, patches=patches, sigs=sigs) part[mm_cfg.SCRUBBER_URL_HEADER] = url finally: os.umask(omask) else: url = "<" + part.get(mm_cfg.SCRUBBER_URL_HEADER, "N/A") + ">" replace_payload_by_text( part, _( """\ An HTML attachment was scrubbed... URL: %(url)s """ ), lcset, ) elif ctype == "message/rfc822": if in_pipeline: omask = os.umask(002) try: url = save_attachment(mlist, part, dir) part[mm_cfg.SCRUBBER_URL_HEADER] = url finally: os.umask(omask) else: # This part contains a submessage, so it too needs scrubbing submsg = part.get_payload(0) url = "<" + part.get(mm_cfg.SCRUBBER_URL_HEADER, "N/A") + ">" subject = submsg.get("subject", _("no subject")) subject = Utils.oneline(subject, lcset) date = submsg.get("date", _("no date")) who = submsg.get("from", _("unknown sender")) size = len(str(submsg)) replace_payload_by_text( part, _( """\ An embedded message was scrubbed... From: %(who)s Subject: %(subject)s Date: %(date)s Size: %(size)s URL: %(url)s """ ), lcset, ) # If the message isn't a multipart, then we'll strip it out as an # attachment that would have to be separately downloaded. Pipermail # will transform the url into a hyperlink. elif part.get_payload() and not part.is_multipart(): payload = part.get_payload(decode=True) # XXX Under email 2.5, it is possible that payload will be None. # This can happen when you have a Content-Type: multipart/* with # only one part and that part has two blank lines between the # first boundary and the end boundary. In email 3.0 you end up # with a string in the payload. I think in this case it's safe to # ignore the part. if payload is None or payload.strip() == "": continue if in_pipeline: omask = os.umask(002) try: url = save_attachment(mlist, part, dir, patches=patches, sigs=sigs) part[mm_cfg.SCRUBBER_URL_HEADER] = url finally: os.umask(omask) else: ctype = part.get_content_type() size = len(payload) url = "<" + part.get(mm_cfg.SCRUBBER_URL_HEADER, "N/A") + ">" desc = part.get("content-description", _("not available")) desc = Utils.oneline(desc, lcset) filename = part.get_filename(_("not available")) filename = Utils.oneline(filename, lcset) replace_payload_by_text( part, _( """\ A non-text attachment was scrubbed... Name: %(filename)s Type: %(ctype)s Size: %(size)d bytes Desc: %(desc)s URL: %(url)s """ ), lcset, ) outer = False # We still have to sanitize multipart messages to flat text because # Pipermail can't handle messages with list payloads. This is a kludge; # def (n) clever hack ;). if msg.is_multipart() and not in_pipeline: # By default we take the charset of the first text/plain part in the # message, but if there was none, we'll use the list's preferred # language's charset. if not charset or charset == "us-ascii": charset = lcset_out else: # normalize to the output charset if input/output are different charset = Charset(charset).output_charset or charset # We now want to concatenate all the parts which have been scrubbed to # text/plain, into a single text/plain payload. We need to make sure # all the characters in the concatenated string are in the same # encoding, so we'll use the 'replace' key in the coercion call. # BAW: Martin's original patch suggested we might want to try # generalizing to utf-8, and that's probably a good idea (eventually). text = [] for part in msg.walk(): # TK: bug-id 1099138 and multipart # MAS test payload - if part may fail if there are no headers. if not part.get_payload() or part.is_multipart(): continue # All parts should be scrubbed to text/plain by now, except # if sanitize == 2, there could be text/html parts so keep them # but skip any other parts. partctype = part.get_content_type() if partctype <> "text/plain" and (partctype <> "text/html" or sanitize <> 2): text.append(_("Skipped content of type %(partctype)s\n")) continue try: t = part.get_payload(decode=True) or "" # MAS: TypeError exception can occur if payload is None. This # was observed with a message that contained an attached # message/delivery-status part. Because of the special parsing # of this type, this resulted in a text/plain sub-part with a # null body. See bug 1430236. except (binascii.Error, TypeError): t = part.get_payload() or "" # TK: get_content_charset() returns 'iso-2022-jp' for internally # crafted (scrubbed) 'euc-jp' text part. So, first try # get_charset(), then get_content_charset() for the parts # which are already embeded in the incoming message. partcharset = part.get_charset() if partcharset: partcharset = str(partcharset) else: partcharset = part.get_content_charset() if partcharset and partcharset <> charset: try: t = unicode(t, partcharset, "replace") except (UnicodeError, LookupError, ValueError, AssertionError): # We can get here if partcharset is bogus in come way. # Replace funny characters. We use errors='replace' t = unicode(t, "ascii", "replace") try: # Should use HTML-Escape, or try generalizing to UTF-8 t = t.encode(charset, "replace") except (UnicodeError, LookupError, ValueError, AssertionError): # if the message charset is bogus, use the list's. t = t.encode(lcset, "replace") # Separation is useful if isinstance(t, StringType): # omit empty parts if t.strip(" \t\r\n") != "": if not t.endswith("\n"): t += "\n" if mm_cfg.SCRUBBER_ARCHIVE_ALL_TEXT: # Add link to archived part if it wasn't archived already url = "URL: <" + part.get(mm_cfg.SCRUBBER_URL_HEADER, "N/A") + ">\n" if not t.endswith(url): t = url + t filename = part.get_filename() if filename: filename = Utils.oneline(filename, lcset) t = "Name: " + filename + "\n" + t text.append(t) # Now join the text and set the payload sep = _("-------------- next part --------------\n") # The i18n separator is in the list's charset. Coerce it to the # message charset. try: s = unicode(sep, lcset, "replace") sep = s.encode(charset, "replace") except (UnicodeError, LookupError, ValueError, AssertionError): pass replace_payload_by_text(msg, sep.join(text), charset) if format: msg.set_param("Format", format) if delsp: msg.set_param("DelSp", delsp) if in_pipeline: (patches, sigs) = process_signatures(mlist, patches, sigs) if patches: # X-Patches-Received: PatchID1=filename; PatchID2=filename processed = {} for p in patches: processed[p["id"]] = p["file"] msg.add_header("X-Patches-Received", None, **processed) if sigs: # output header in RFC compliant way, if multiple sigs with multiple keys per one patch, then delimited by dot, like: # X-Sigs-Received: PatchID1=KeyID1; PatchID2=KeyID2.KeyID3 processed = {} for s in sigs: processed[s["phash"]] = processed.get(s["phash"], []) + [s["key"]] for k in processed.keys(): # dot does not need escaping processed[k] = ".".join(processed[k]) msg.add_header("X-Sigs-Received", None, **processed) return msg
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, mlist=mlist, contexts=AUTH_CONTEXTS, user=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.parse.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>'] = '' page_text = mlist.ParseTags('options.html', replacements, userlang) if not (mlist.digestable or mlist.getMemberOption(user, mm_cfg.Digests)): page_text = DIGRE.sub('', page_text) doc.AddItem(page_text)
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())
def save_attachment(mlist, msg, dir, filter_html=True, patches=None, sigs=None): fsdir = os.path.join(mlist.archive_dir(), dir) makedirs(fsdir) # Figure out the attachment type and get the decoded data decodedpayload = msg.get_payload(decode=True) # BAW: mimetypes ought to handle non-standard, but commonly found types, # e.g. image/jpg (should be image/jpeg). For now we just store such # things as application/octet-streams since that seems the safest. ctype = msg.get_content_type() # i18n file name is encoded lcset = Utils.GetCharSet(mlist.preferred_language) filename = Utils.oneline(msg.get_filename(""), lcset) # filename, fnext = os.path.splitext(filename) #won't work with double extensions like .patch.sig fnext = None try: (filename, fnext) = filename.split(".", 1) if filename == "": filename = None if fnext != "": fnext = "." + fnext except: pass # For safety, we should confirm this is valid ext for content-type # but we can use fnext if we introduce fnext filtering if mm_cfg.SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION: # HTML message doesn't have filename :-( # if it's text/plain, use '.txt', otherwise we'd end with '.ksh' or so ext = fnext or guess_extension(ctype, ".txt") else: ext = guess_extension(ctype, fnext) if not ext: # We don't know what it is, so assume it's just a shapeless # application/octet-stream, unless the Content-Type: is # message/rfc822, in which case we know we'll coerce the type to # text/plain below. if ctype == "message/rfc822": ext = ".txt" else: ext = ".bin" # Allow only alphanumerics, dash, underscore, and dot ext = sre.sub("", ext) path = None extra = "" sha = "" msgfrom = "" do_write_file = True if mm_cfg.SCRUBBER_ADD_PAYLOAD_HASH_FILENAME or not filename: sha = msg.get(mm_cfg.SCRUBBER_SHA1SUM_HEADER) if sha: # no need to clutter headers del msg[mm_cfg.SCRUBBER_SHA1SUM_HEADER] msgfrom = msg[mm_cfg.SCRUBBER_SIGNEDBY_HEADER] else: sha = sha_new(decodedpayload).hexdigest() # We need a lock to calculate the next attachment number lockfile = os.path.join(fsdir, "attachments.lock") lock = LockFile.LockFile(lockfile) lock.lock() try: # Now base the filename on what's in the attachment, uniquifying it if # necessary. if not filename or mm_cfg.SCRUBBER_DONT_USE_ATTACHMENT_FILENAME: filebase = "attachment" else: # Sanitize the filename given in the message headers parts = pre.split(filename) filename = parts[-1] # Strip off leading dots filename = dre.sub("", filename) # Allow only alphanumerics, dash, underscore, and dot filename = sre.sub("", filename) # If the filename's extension doesn't match the type we guessed, # which one should we go with? For now, let's go with the one we # guessed so attachments can't lie about their type. Also, if the # filename /has/ no extension, then tack on the one we guessed. # The extension was removed from the name above. filebase = filename # Now we're looking for a unique name for this file on the file # system. If msgdir/filebase.ext isn't unique, we'll add a counter # after filebase, e.g. msgdir/filebase-cnt.ext counter = 0 while True: path = os.path.join(fsdir, filebase + extra + ext) # Generally it is not a good idea to test for file existance # before just trying to create it, but the alternatives aren't # wonderful (i.e. os.open(..., O_CREAT | O_EXCL) isn't # NFS-safe). Besides, we have an exclusive lock now, so we're # guaranteed that no other process will be racing with us. if os.path.exists(path): counter += 1 extra = "-%04d" % counter else: break filename = filebase + extra + ext if mm_cfg.SCRUBBER_ADD_PAYLOAD_HASH_FILENAME: # Make content hash to attachment linkdir = os.path.join(fsdir, "..", "links") makedirs(linkdir) if msgfrom: dst = os.path.join(linkdir, msgfrom + "_" + sha) else: dst = os.path.join(linkdir, sha) src = os.path.join(fsdir, filename) try: os.symlink(src, dst) except: syslog("gpg", "Duplicate attachment: %s/%s msgfrom: %s sha1: %s" % (fsdir, filename, msgfrom, sha)) # To deduplicate would need to parse, etc. # filename = os.readlink(dst) # do_write_file = False finally: lock.unlock() if do_write_file: # `path' now contains the unique filename for the attachment. There's # just one more step we need to do. If the part is text/html and # ARCHIVE_HTML_SANITIZER is a string (which it must be or we wouldn't be # here), then send the attachment through the filter program for # sanitization if filter_html and ctype == "text/html": base, ext = os.path.splitext(path) tmppath = base + "-tmp" + ext fp = open(tmppath, "w") try: fp.write(decodedpayload) fp.close() cmd = mm_cfg.ARCHIVE_HTML_SANITIZER % {"filename": tmppath} progfp = os.popen(cmd, "r") decodedpayload = progfp.read() status = progfp.close() if status: syslog("error", "HTML sanitizer exited with non-zero status: %s", status) finally: os.unlink(tmppath) # BAW: Since we've now sanitized the document, it should be plain # text. Blarg, we really want the sanitizer to tell us what the type # if the return data is. :( ext = ".txt" path = base + ".txt" # Is it a message/rfc822 attachment? elif ctype == "message/rfc822": submsg = msg.get_payload() # BAW: I'm sure we can eventually do better than this. :( decodedpayload = Utils.websafe(str(submsg)) fp = open(path, "w") fp.write(decodedpayload) fp.close() # Now calculate the url baseurl = mlist.GetBaseArchiveURL() # Private archives will likely have a trailing slash. Normalize. if baseurl[-1] <> "/": baseurl += "/" url = baseurl + "%s/%s%s%s" % (dir, filebase, extra, ext) if sha: url += "?sha1=" + sha if sigs is not None and (ext.endswith(".sig") or ctype == "application/pgp-signature"): sigs.append({"id": sha, "name": filebase, "file": os.path.join(dir, filename), "url": url}) elif patches is not None and ctype != "text/html" and ctype != "message/rfc822": patches.append({"id": sha, "name": filebase, "file": os.path.join(dir, filename), "url": url}) # A trailing space in url string may save users who are using # RFC-1738 compliant MUA (Not Mozilla). # Trailing space will definitely be a problem with format=flowed. # Bracket the URL instead. # '<' + url + '>' Done by caller instead. return url
def __init__(self, name, value='', size=mm_cfg.TEXTFIELDWIDTH): if isinstance(value, str): safevalue = Utils.websafe(value) else: safevalue = value InputObj.__init__(self, name, "TEXT", safevalue, checked=0, size=size)