def _setValue(self, mlist, property, val, doc): # Do value conversion from web representation to internal # representation. try: if property == 'bounce_processing': val = int(val) elif property == 'bounce_score_threshold': val = float(val) elif property == 'bounce_info_stale_after': val = days(int(val)) elif property == 'bounce_you_are_disabled_warnings': val = int(val) elif property == 'bounce_you_are_disabled_warnings_interval': val = days(int(val)) elif property == 'bounce_notify_owner_on_disable': val = int(val) elif property == 'bounce_notify_owner_on_removal': val = int(val) except ValueError: doc.addError( _("""Bad value for <a href="?VARHELP=bounce/%(property)s" >%(property)s</a>: %(val)s"""), tag=_('Error: ')) return GUIBase._setValue(self, mlist, property, val, doc)
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 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 do_discard(mlist, msg): sender = msg.get_sender() # Do we forward auto-discards to the list owners? if mlist.forward_auto_discards: lang = mlist.preferred_language varhelp = '%s/?VARHELP=privacy/sender/discard_these_nonmembers' % \ mlist.GetScriptURL('admin', absolute=1) nmsg = Message.UserNotification(mlist.GetOwnerEmail(), mlist.GetBouncesEmail(), _('Auto-discard notification'), lang=lang) nmsg.set_type('multipart/mixed') text = MIMEText(Utils.wrap(_( 'The attached message has been automatically discarded.')), _charset=Utils.GetCharSet(lang)) nmsg.attach(text) decrypted = msg.get('X-Mailman-SLS-decrypted', '').lower() if decrypted == 'yes': syslog('gpg', 'forwarding only headers of message from %s to listmaster to notify discard since message was decrypted', sender) msgtext = msg.as_string() (header, body) = msgtext.split("\n\n", 1) nmsg.attach(MIMEText(header)) else: nmsg.attach(MIMEMessage(msg)) nmsg.send(mlist) # Discard this sucker raise Errors.DiscardMessage
def MailmanLogo(): t = Table(border=0, width='100%') version = mm_cfg.VERSION mmlink = _("Delivered by Mailman") pylink = _("Python Powered") gnulink = _("GNU's Not Unix") if mm_cfg.SITE_LINK: sitelink = mm_cfg.SITE_TEXT if mm_cfg.IMAGE_LOGOS: def logo(file, alt, base=mm_cfg.IMAGE_LOGOS): return '<img src="%s" alt="%s" border="0" />' % \ (base + file, alt) mmlink = logo(DELIVERED_BY, mmlink) pylink = logo(PYTHON_POWERED, pylink) gnulink = logo(GNU_HEAD, gnulink) if mm_cfg.SITE_LINK: sitelink = logo(mm_cfg.SITE_LOGO, sitelink, "") mmlink = Link(MAILMAN_URL, mmlink + _('<br>version %(version)s')) pylink = Link(PYTHON_URL, pylink) gnulink = Link(GNU_URL, gnulink) links = [mmlink, pylink, gnulink] if mm_cfg.SITE_LINK: if mm_cfg.SITE_URL: sitelink = Link(mm_cfg.SITE_URL, sitelink) links.append(sitelink) t.AddRow(links) return t
def GetConfigSubCategories(self, category): if category == 'members': return [('list', _('Membership List')), ('add', _('Mass Subscription')), ('remove', _('Mass Removal')), ] return None
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 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, TupleType) 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 cgidata.has_key(uploadprop) and cgidata[uploadprop].value: val = cgidata[uploadprop].value elif not cgidata.has_key(property): continue elif isinstance(cgidata[property], ListType): 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, 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)
def maybe_forward(mlist, msg): # Does the list owner want to get non-matching bounce messages? # If not, simply discard it. if mlist.bounce_unrecognized_goes_to_list_owner: adminurl = mlist.GetScriptURL('admin', absolute=1) + '/bounce' mlist.ForwardMessage(msg, text=_("""\ The attached message was received as a bounce, but either the bounce format was not recognized, or no member addresses could be extracted from it. This mailing list has been configured to send all unrecognized bounce messages to the list administrator(s). For more information see: %(adminurl)s """), subject=_('Uncaught bounce notification'), tomoderators=0) syslog('bounce', '%s: forwarding unrecognized, message-id: %s', mlist.internal_name(), msg.get('message-id', 'n/a')) else: syslog('bounce', '%s: discarding unrecognized, message-id: %s', mlist.internal_name(), msg.get('message-id', 'n/a'))
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 OpenIDOption(self, lang): #from Cgi import client container = Container() hostname = mm_cfg.DEFAULT_URL_HOST hostid = mm_cfg.DEFAULT_OID_CONSUMER # if self.private_roster == 0: # either = _('<b><i>either</i></b> ') # else: # either = '' # realname = self.real_name container.AddItem(Hidden('language', lang)) if not self.private_roster: container.AddItem(_("Click here for the list of ") + self.real_name + _(" subscribers: ")) container.AddItem(SubmitButton('LoginThrough', _("Visit Subscriber list"))) else: if self.private_roster == 1: only = _('members') whom = _('Address:') else: only = _('the list administrator') whom = _('Admin address:') container.AddItem(_("<B> <p>Use Common ") # + whom[:-1].lower() + _("Authentication to access" " <a href='http://%(hostname)s:%(hostid)s'> all lists</a> <p> </B> ") + _("<p> <B> You can enable Common Authentication <a href='http://%(hostname)s/mailman/openidreg'> Enable </a> </B>") # + whom + " ") container.AddItem("</center>") return container
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 _setValue(self, mlist, property, val, doc): # Watch for the special, immediate action attributes if property == "_new_volume" and val: mlist.bump_digest_volume() volume = mlist.volume number = mlist.next_digest_number doc.AddItem( _( """The next digest will be sent as volume %(volume)s, number %(number)s""" ) ) elif property == "_send_digest_now" and val: status = mlist.send_digest_now() if status: doc.AddItem(_("""A digest has been sent.""")) else: doc.AddItem(_("""There was no digest to send.""")) else: # Everything else... if property in ("digest_header", "digest_footer"): val = self._convertString(mlist, property, ALLOWEDS, val, doc) if val is None: # There was a problem, so don't set it return GUIBase._setValue(self, mlist, property, val, doc)
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 _setValue(self, mlist, property, val, doc): # Do value conversion from web representation to internal # representation. try: if property == 'bounce_processing': val = int(val) elif property == 'bounce_score_threshold': val = float(val) elif property == 'bounce_info_stale_after': val = days(int(val)) elif property == 'bounce_you_are_disabled_warnings': val = int(val) elif property == 'bounce_you_are_disabled_warnings_interval': val = days(int(val)) elif property == 'bounce_notify_owner_on_disable': val = int(val) elif property == 'bounce_notify_owner_on_removal': val = int(val) except ValueError: doc.addError( _("""Bad value for <a href="?VARHELP=bounce/%(property)s" >%(property)s</a>: %(val)s"""), tag = _('Error: ')) return GUIBase._setValue(self, mlist, property, val, doc)
def GetConfigSubCategories(self, category): if category == 'privacy': return [('subscribing', _('Subscription rules')), ('sender', _('Sender filters')), ('recipient', _('Recipient filters')), ('spam', _('Spam filters')), ] return None
def addError(self, errmsg, tag=None): if tag is None: tag = _('Error: ') self.AddItem( Header( 3, Bold(FontAttr(_(tag), color=mm_cfg.WEB_ERROR_COLOR, size='+2')).Format() + Italic(errmsg).Format()))
def GetConfigSubCategories(self, category): if category == 'members': return [('list', _('Membership List')), ('add', _('Mass Subscription')), ('remove', _('Mass Removal')), ('change', _('Address Change')), ] return None
def RestrictedListMessage(self, which, restriction): if not restriction: return '' elif restriction == 1: return _('''(<i>%(which)s is only available to the list members.</i>)''') else: return _('''(<i>%(which)s is only available to the list administrator.</i>)''')
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 convert(mlist): for attr in ('msg_header', 'msg_footer', 'digest_header', 'digest_footer', 'autoresponse_postings_text', 'autoresponse_admin_text', 'autoresponse_request_text'): s = getattr(mlist, attr) t = Utils.to_dollar(s) setattr(mlist, attr, t) mlist.use_dollar_strings = 1 print _('Saving list') mlist.Save()
def RestrictedListMessage(self, which, restriction): if not restriction: return '' elif restriction == 1: return _( '''(<i>%(which)s is only available to the list members.</i>)''') else: return _('''(<i>%(which)s is only available to the list administrator.</i>)''')
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 _setValue(self, mlist, property, val, doc): if property == 'real_name' and \ val.lower() != mlist.internal_name().lower(): # These values can't differ by other than case doc.addError(_("""<b>real_name</b> attribute not changed! It must differ from the list's name by case only.""")) elif property == 'new_member_options': # Get current value because there are valid bits not in OPTIONS. # If we're the admin CGI, we then process the bits in OPTIONS, # turning them on or off as appropriate. Otherwise we process all # the bits in mm_cfg.OPTINFO so that config_list can set and reset # them. newopts = mlist.new_member_options if isinstance(doc, Document): opts = OPTIONS else: opts = mm_cfg.OPTINFO for opt in opts: bitfield = mm_cfg.OPTINFO[opt] if opt in val: newopts |= bitfield else: newopts &= ~bitfield mlist.new_member_options = newopts elif property == 'subject_prefix': # Convert any html entities to Unicode mlist.subject_prefix = Utils.canonstr( val, mlist.preferred_language) elif property == 'info': if val != mlist.info: if Utils.suspiciousHTML(val): doc.addError(_("""The <b>info</b> attribute you saved contains suspicious HTML that could potentially expose your users to cross-site scripting attacks. This change has therefore been rejected. If you still want to make these changes, you must have shell access to your Mailman server. This change can be made with bin/withlist or with bin/config_list by setting mlist.info. """)) else: mlist.info = val elif property == 'admin_member_chunksize' and (val < 1 or not isinstance(val, int)): doc.addError(_("""<b>admin_member_chunksize</b> attribute not changed! It must be an integer > 0.""")) elif property == 'host_name': try: Utils.ValidateEmail('user@' + val) except Errors.EmailAddressError: doc.addError(_("""<b>host_name</b> attribute not changed! It must be a valid domain name.""")) else: GUIBase._setValue(self, mlist, property, val, doc) else: GUIBase._setValue(self, mlist, property, val, doc)
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 scrub_text(self, part): # Plain text scrubber. omask = os.umask(002) try: url = save_attachment(self.mlist, part, self.dir) finally: os.umask(omask) filename = part.get_filename(_('not available')) filename = Utils.oneline(filename, self.lcset) self.msgtexts.append(unicode(_("""\ An embedded and charset-unspecified text was scrubbed... Name: %(filename)s URL: %(url)s """), self.lcset))
def GetConfigInfo(self, mlist, category, subcat=None): if category <> 'topics': return None WIDTH = mm_cfg.TEXTFIELDWIDTH return [ _('List topic keywords'), ('topics_enabled', mm_cfg.Radio, (_('Disabled'), _('Enabled')), 0, _('''Should the topic filter be enabled or disabled?'''), _("""The topic filter categorizes each incoming email message according to <a href="http://docs.python.org/library/re.html">regular expression filters</a> you specify below. If the message's <code>Subject:</code> or <code>Keywords:</code> header contains a match against a topic filter, the message is logically placed into a topic <em>bucket</em>. Each user can then choose to only receive messages from the mailing list for a particular topic bucket (or buckets). Any message not categorized in a topic bucket registered with the user is not delivered to the list. <p>Note that this feature only works with regular delivery, not digest delivery. <p>The body of the message can also be optionally scanned for <code>Subject:</code> and <code>Keywords:</code> headers, as specified by the <a href="?VARHELP=topics/topics_bodylines_limit">topics_bodylines_limit</a> configuration variable.""")), ('topics_bodylines_limit', mm_cfg.Number, 5, 0, _('How many body lines should the topic matcher scan?'), _("""The topic matcher will scan this many lines of the message body looking for topic keyword matches. Body scanning stops when either this many lines have been looked at, or a non-header-like body line is encountered. By setting this value to zero, no body lines will be scanned (i.e. only the <code>Keywords:</code> and <code>Subject:</code> headers will be scanned). By setting this value to a negative number, then all body lines will be scanned until a non-header-like line is encountered. """)), ('topics', mm_cfg.Topics, 0, 0, _('Topic keywords, one per line, to match against each message.'), _("""Each topic keyword is actually a regular expression, which is matched against certain parts of a mail message, specifically the <code>Keywords:</code> and <code>Subject:</code> message headers. Note that the first few lines of the body of the message can also contain a <code>Keywords:</code> and <code>Subject:</code> "header" on which matching is also performed.""")), ]
def create(mlist, cgi=False, nolock=False, quiet=False): if mlist is None: return listname = mlist.internal_name() fieldsz = len(listname) + len('-unsubscribe') if cgi: # If a list is being created via the CGI, the best we can do is send # an email message to mailman-owner requesting that the proper aliases # be installed. sfp = StringIO() if not quiet: print(_(""" The mailing list `%(listname)s' has been created via the through-the-web interface. In order to complete the activation of this mailing list, the proper /etc/aliases (or equivalent) file must be updated. The program `newaliases' may also have to be run. Here are the entries for the /etc/aliases file: """), file=sfp) outfp = sfp else: if not quiet: print( C_("""\ To finish creating your mailing list, you must edit your /etc/aliases (or equivalent) file by adding the following lines, and possibly running the `newaliases' program: """)) print(C_("""\ ## %(listname)s mailing list""")) outfp = sys.stdout # Common path for k, v in makealiases(listname): print(k + ':', ((fieldsz - len(k)) * ' '), v, file=outfp) # If we're using the command line interface, we're done. For ttw, we need # to actually send the message to mailman-owner now. if not cgi: print(file=outfp) return # Send the message to the site -owner so someone can do something about # this request. siteowner = Utils.get_site_email(extra='owner') # Should this be sent in the site list's preferred language? msg = Message.UserNotification( siteowner, siteowner, _('Mailing list creation request for list %(listname)s'), sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) msg.send(mlist)
def process(res, args): mlist = res.mlist password = None address = None argnum = 0 for arg in args: if arg.startswith('address='): address = arg[8:] elif argnum == 0: password = arg else: res.results.append(_('Usage:')) res.results.append(gethelp(mlist)) return STOP argnum += 1 # Fill in empty defaults if address is None: realname, address = parseaddr(res.msg['from']) if not mlist.isMember(address): listname = mlist.real_name res.results.append( _('%(address)s is not a member of the %(listname)s mailing list')) return STOP # If we're doing admin-approved unsubs, don't worry about the password if mlist.unsubscribe_policy: try: mlist.DeleteMember(address, 'mailcmd') except Errors.MMNeedApproval: res.results.append( _("""\ Your unsubscription request has been forwarded to the list administrator for approval.""")) elif password is None: # No password was given, so we need to do a mailback confirmation # instead of unsubscribing them here. cpaddr = mlist.getMemberCPAddress(address) mlist.ConfirmUnsubscription(cpaddr) # We don't also need to send a confirmation to this command res.respond = 0 else: # No admin approval is necessary, so we can just delete them if the # passwords match. oldpw = mlist.getMemberPassword(address) if oldpw != password: res.results.append(_('You gave the wrong password')) return STOP mlist.ApprovedDeleteMember(address, 'mailcmd') res.results.append(_('Unsubscription request succeeded.'))
def _setValue(self, mlist, property, val, doc): if property in ('filter_mime_types', 'pass_mime_types'): types = [] for spectype in [s.strip() for s in val.splitlines()]: ok = 1 slashes = spectype.count('/') if slashes == 0 and not spectype: ok = 0 elif slashes == 1: maintype, subtype = [s.strip().lower() for s in spectype.split('/')] if not maintype or not subtype: ok = 0 elif slashes > 1: ok = 0 if not ok: doc.addError(_('Bad MIME type ignored: %(spectype)s')) else: types.append(spectype.strip().lower()) if property == 'filter_mime_types': mlist.filter_mime_types = types elif property == 'pass_mime_types': mlist.pass_mime_types = types elif property in ('filter_filename_extensions', 'pass_filename_extensions'): fexts = [] for ext in [s.strip() for s in val.splitlines()]: fexts.append(ext.lower()) if property == 'filter_filename_extensions': mlist.filter_filename_extensions = fexts elif property == 'pass_filename_extensions': mlist.pass_filename_extensions = fexts else: GUIBase._setValue(self, mlist, property, val, doc)
def _setValue(self, mlist, property, val, doc): # Watch for the special, immediate action attributes if property == '_mass_catchup' and val: mlist.usenet_watermark = None doc.AddItem(_('Mass catchup completed')) else: GUIBase._setValue(self, mlist, property, val, doc)
def __init__(self, basedir = None, reload = 1, database = None): # If basedir isn't provided, assume the current directory if basedir is None: self.basedir = os.getcwd() else: basedir = os.path.expanduser(basedir) self.basedir = basedir self.database = database # If the directory doesn't exist, create it. This code shouldn't get # run anymore, we create the directory in Archiver.py. It should only # get used by legacy lists created that are only receiving their first # message in the HTML archive now -- Marc try: os.stat(self.basedir) except os.error, errdata: errno, errmsg = errdata if errno != 2: raise os.error, errdata else: self.message(_('Creating archive directory ') + self.basedir) omask = os.umask(0) try: os.mkdir(self.basedir, self.DIRMODE) finally: os.umask(omask)
def GetStandardReplacement(self): 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. d = { '<mm-mailman-footer>' : self.GetMailmanFooter(), '<mm-list-name>' : self.real_name, '<mm-email-user>' : self._internal_name, '<mm-list-description>' : 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, '<mm-post-thread-addr>' : '%s' % self.GetListStartThreadEmail(), # added for Dlists }
def _setValue(self, mlist, property, val, doc): if property in ('filter_mime_types', 'pass_mime_types'): types = [] for spectype in [s.strip() for s in val.splitlines()]: ok = 1 slashes = spectype.count('/') if slashes == 0 and not spectype: ok = 0 elif slashes == 1: maintype, subtype = [ s.strip().lower() for s in spectype.split('/') ] if not maintype or not subtype: ok = 0 elif slashes > 1: ok = 0 if not ok: doc.addError(_('Bad MIME type ignored: %(spectype)s')) else: types.append(spectype.strip().lower()) if property == 'filter_mime_types': mlist.filter_mime_types = types elif property == 'pass_mime_types': mlist.pass_mime_types = types elif property in ('filter_filename_extensions', 'pass_filename_extensions'): fexts = [] for ext in [s.strip() for s in val.splitlines()]: fexts.append(ext.lower()) if property == 'filter_filename_extensions': mlist.filter_filename_extensions = fexts elif property == 'pass_filename_extensions': mlist.pass_filename_extensions = fexts else: GUIBase._setValue(self, mlist, property, val, doc)
def __init__(self, basedir=None, reload=1, database=None): # If basedir isn't provided, assume the current directory if basedir is None: self.basedir = os.getcwd() else: basedir = os.path.expanduser(basedir) self.basedir = basedir self.database = database # If the directory doesn't exist, create it. This code shouldn't get # run anymore, we create the directory in Archiver.py. It should only # get used by legacy lists created that are only receiving their first # message in the HTML archive now -- Marc try: os.stat(self.basedir) except os.error, errdata: errno, errmsg = errdata if errno != 2: raise os.error, errdata else: self.message(_('Creating archive directory ') + self.basedir) omask = os.umask(0) try: os.mkdir(self.basedir, self.DIRMODE) finally: os.umask(omask)
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 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 _setValue(self, mlist, property, val, doc): if property == 'real_name' and \ val.lower() <> str(mlist.real_name.lower()): # These values can't differ by other than case doc.addError(_("""<b>real_name</b> attribute not changed! It must differ from the list's name by case only.""")) elif property == 'new_member_options': # Get current value because there are valid bits not in OPTIONS. # If we're the admin CGI, we then process the bits in OPTIONS, # turning them on or off as appropriate. Otherwise we process all # the bits in mm_cfg.OPTINFO so that config_list can set and reset # them. newopts = mlist.new_member_options if isinstance(doc, Document): opts = OPTIONS else: opts = mm_cfg.OPTINFO for opt in opts: bitfield = mm_cfg.OPTINFO[opt] if opt in val: newopts |= bitfield else: newopts &= ~bitfield mlist.new_member_options = newopts elif property == 'subject_prefix': # Convert any html entities to Unicode mlist.subject_prefix = Utils.canonstr( val, mlist.preferred_language) elif property == 'info': if val <> mlist.info: if 0 and Utils.suspiciousHTML(val): doc.addError(_("""The <b>info</b> attribute you saved contains suspicious HTML that could potentially expose your users to cross-site scripting attacks. This change has therefore been rejected. If you still want to make these changes, you must have shell access to your Mailman server. This change can be made with bin/withlist or with bin/config_list by setting mlist.info. """)) else: mlist.info = val elif property == 'admin_member_chunksize' and (val < 1 or not isinstance(val, IntType)): doc.addError(_("""<b>admin_member_chunksize</b> attribute not changed! It must be an integer > 0.""")) else: GUIBase._setValue(self, mlist, property, val, doc)
def process(res, args): mlist = res.mlist password = None address = None argnum = 0 for arg in args: if arg.startswith('address='): address = arg[8:] elif argnum == 0: password = arg else: res.results.append(_('Usage:')) res.results.append(gethelp(mlist)) return STOP argnum += 1 # Fill in empty defaults if address is None: realname, address = parseaddr(res.msg['from']) if not mlist.isMember(address): listname = mlist.real_name res.results.append( _('%(address)s is not a member of the %(listname)s mailing list')) return STOP # If we're doing admin-approved unsubs, don't worry about the password if mlist.unsubscribe_policy: try: mlist.DeleteMember(address, 'mailcmd') except Errors.MMNeedApproval: res.results.append(_("""\ Your unsubscription request has been forwarded to the list administrator for approval.""")) elif password is None: # No password was given, so we need to do a mailback confirmation # instead of unsubscribing them here. cpaddr = mlist.getMemberCPAddress(address) mlist.ConfirmUnsubscription(cpaddr) # We don't also need to send a confirmation to this command res.respond = 0 else: # No admin approval is necessary, so we can just delete them if the # passwords match. oldpw = mlist.getMemberPassword(address) if oldpw <> password: res.results.append(_('You gave the wrong password')) return STOP mlist.ApprovedDeleteMember(address, 'mailcmd') res.results.append(_('Unsubscription request succeeded.'))
def process(mlist, msg, msgdata): # Short circuit if we've already calculated the recipients list, # regardless of whether the list is empty or not. if msgdata.has_key('recips'): return # Should the original sender should be included in the recipients list? include_sender = 1 sender = msg.get_sender() try: if mlist.getMemberOption(sender, mm_cfg.DontReceiveOwnPosts): include_sender = 0 except Errors.NotAMemberError: pass # Support for urgent messages, which bypasses digests and disabled # delivery and forces an immediate delivery to all members Right Now. We # are specifically /not/ allowing the site admins password to work here # because we want to discourage the practice of sending the site admin # password through email in the clear. (see also Approve.py) missing = [] password = msg.get('urgent', missing) if password is not missing: if mlist.Authenticate((mm_cfg.AuthListPoster, mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin), password): recips = mlist.getMemberCPAddresses(mlist.getRegularMemberKeys() + mlist.getDigestMemberKeys()) msgdata['recips'] = recips return else: # Bad Urgent: password, so reject it instead of passing it on. I # think it's better that the sender know they screwed up than to # deliver it normally. realname = mlist.real_name text = _("""\ Your urgent message to the %(realname)s mailing list was not authorized for delivery. The original message as received by Mailman is attached. """) raise Errors.RejectMessage, Utils.wrap(text) # Calculate the regular recipients of the message recips = [mlist.getMemberCPAddress(m) for m in mlist.getRegularMemberKeys() if mlist.getDeliveryStatus(m) == ENABLED] # Remove the sender if they don't want to receive their own posts if not include_sender: try: recips.remove(mlist.getMemberCPAddress(sender)) except (Errors.NotAMemberError, ValueError): # Sender does not want to get copies of their own messages (not # metoo), but delivery to their address is disabled (nomail). Or # the sender is not a member of the mailing list. pass # Handle topic classifications do_topic_filters(mlist, msg, msgdata, recips) # Regular delivery exclude/include (if in/not_in To: or Cc:) lists recips = do_exclude(mlist, msg, msgdata, recips) recips = do_include(mlist, msg, msgdata, recips) # Bookkeeping msgdata['recips'] = recips
def FormatUmbrellaNotice(self, user, type): addr = self.GetMemberAdminEmail(user) if self.umbrella_list: return _("(Note - you are subscribing to a list of mailing lists, " "so the %(type)s notice will be sent to the admin address" " for your membership, %(addr)s.)<p>") else: return ""
def _postValidate(self, mlist, doc): if not mlist.reply_to_address.strip() and \ mlist.reply_goes_to_list == 2: # You can't go to an explicit address that is blank doc.addError(_("""You cannot add a Reply-To: to an explicit address if that address is blank. Resetting these values.""")) mlist.reply_to_address = '' mlist.reply_goes_to_list = 0
def process(res, args): # Get the help text introduction mlist = res.mlist # Since this message is personalized, add some useful information if the # address requesting help is a member of the list. msg = res.msg for sender in msg.get_senders(): if mlist.isMember(sender): memberurl = mlist.GetOptionsURL(sender, absolute=1) urlhelp = _( 'You can access your personal options via the following url:') res.results.append(urlhelp) res.results.append(memberurl) # Get a blank line in the output. res.results.append('') break # build the specific command helps from the module docstrings modhelps = {} import Mailman.Commands path = os.path.dirname(os.path.abspath(Mailman.Commands.__file__)) for file in os.listdir(path): if not file.startswith('cmd_') or not file.endswith('.py'): continue module = os.path.splitext(file)[0] modname = 'Mailman.Commands.' + module try: __import__(modname) except ImportError: continue cmdname = module[4:] help = None if hasattr(sys.modules[modname], 'gethelp'): help = sys.modules[modname].gethelp(mlist) if help: modhelps[cmdname] = help # Now sort the command helps helptext = [] keys = list(modhelps.keys()) keys.sort() for cmd in keys: helptext.append(modhelps[cmd]) commands = EMPTYSTRING.join(helptext) # Now craft the response helptext = Utils.maketext( 'help.txt', { 'listname': mlist.real_name, 'version': mm_cfg.VERSION, 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), 'requestaddr': mlist.GetRequestEmail(), 'adminaddr': mlist.GetOwnerEmail(), 'commands': commands, }, mlist=mlist, lang=res.msgdata['lang'], raw=1) # Now add to the response res.results.append('help') res.results.append(helptext)
def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout print >> fd, _(__doc__.replace('%', '%%')) if msg: print >> fd, msg sys.exit(code)
def process(res, args): mlist = res.mlist if len(args) <> 1: res.results.append(_('Usage:')) res.results.append(gethelp(mlist)) return STOP cookie = args[0] try: results = mlist.ProcessConfirmation(cookie, res.msg) except Errors.MMBadConfirmation, e: # Express in approximate days days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1) + 0.5) res.results.append( _("""\ Invalid confirmation string. Note that confirmation strings expire approximately %(days)s days after the initial subscription request. If your confirmation has expired, please try to re-submit your original request or message."""))
class T: DIRMODE = 0755 # Mode to give to created directories FILEMODE = 0644 # Mode to give to created files INDEX_EXT = ".html" # Extension for indexes def __init__(self, basedir=None, reload=1, database=None): # If basedir isn't provided, assume the current directory if basedir is None: self.basedir = os.getcwd() else: basedir = os.path.expanduser(basedir) self.basedir = basedir self.database = database # If the directory doesn't exist, create it. This code shouldn't get # run anymore, we create the directory in Archiver.py. It should only # get used by legacy lists created that are only receiving their first # message in the HTML archive now -- Marc try: os.stat(self.basedir) except os.error, errdata: errno, errmsg = errdata if errno != 2: raise os.error, errdata else: self.message(_('Creating archive directory ') + self.basedir) omask = os.umask(0) try: os.mkdir(self.basedir, self.DIRMODE) finally: os.umask(omask) # Try to load previously pickled state try: if not reload: raise IOError f = open(os.path.join(self.basedir, 'pipermail.pck'), 'r') self.message(_('Reloading pickled archive state')) d = pickle.load(f) f.close() for key, value in d.items(): setattr(self, key, value) except (IOError, EOFError): # No pickled version, so initialize various attributes self.archives = [] # Archives self._dirty_archives = [] # Archives that will have to be updated self.sequence = 0 # Sequence variable used for # numbering articles self.update_TOC = 0 # Does the TOC need updating? # # make the basedir variable work when passed in as an __init__ arg # and different from the one in the pickle. Let the one passed in # as an __init__ arg take precedence if it's stated. This way, an # archive can be moved from one place to another and still work. # if basedir != self.basedir: self.basedir = basedir
def remove(mlist, cgi=False): listname = mlist.internal_name() fieldsz = len(listname) + len('-unsubscribe') if cgi: # If a list is being removed via the CGI, the best we can do is send # an email message to mailman-owner requesting that the appropriate # aliases be deleted. sfp = StringIO() print(_("""\ The mailing list `%(listname)s' has been removed via the through-the-web interface. In order to complete the de-activation of this mailing list, the appropriate /etc/aliases (or equivalent) file must be updated. The program `newaliases' may also have to be run. Here are the entries in the /etc/aliases file that should be removed: """), file=sfp) outfp = sfp else: print( C_(""" To finish removing your mailing list, you must edit your /etc/aliases (or equivalent) file by removing the following lines, and possibly running the `newaliases' program: ## %(listname)s mailing list""")) outfp = sys.stdout # Common path for k, v in makealiases(listname): print(k + ':', ((fieldsz - len(k)) * ' '), v, file=outfp) # If we're using the command line interface, we're done. For ttw, we need # to actually send the message to mailman-owner now. if not cgi: print(file=outfp) return siteowner = Utils.get_site_email(extra='owner') # Should this be sent in the site list's preferred language? msg = Message.UserNotification( siteowner, siteowner, _('Mailing list removal request for list %(listname)s'), sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) msg['Date'] = email.utils.formatdate(localtime=1) outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) outq.enqueue(msg, recips=[siteowner], nodecorate=1)
def update_archive(self, archive): self.archive = archive self.message(_("Updating index files for archive [%(archive)s]")) arcdir = os.path.join(self.basedir, archive) self.__set_parameters(archive) for hdr in ('Date', 'Subject', 'Author'): self._update_simple_index(hdr, archive, arcdir) self._update_thread_index(archive, arcdir)