def matches_p(sender, nonmembers, listname): # First strip out all the regular expressions and listnames plainaddrs = [addr for addr in nonmembers if not (addr.startswith('^') or addr.startswith('@'))] addrdict = Utils.List2Dict(plainaddrs, foldcase=1) if addrdict.has_key(sender): return 1 # Now do the regular expression matches for are in nonmembers: if are.startswith('^'): try: cre = re.compile(are, re.IGNORECASE) except re.error: continue if cre.search(sender): return 1 elif are.startswith('@'): # XXX Needs to be reviewed for list@domain names. try: mname = are[1:].lower().strip() if mname == listname: # don't reference your own list syslog('error', '*_these_nonmembers in %s references own list', listname) else: mother = MailList(mname, lock=0) if mother.isMember(sender): return 1 except Errors.MMUnknownListError: syslog('error', '*_these_nonmembers in %s references non-existent list %s', listname, mname) return 0
def _create(self, team_name): """Create a new mailing list.""" # Create the mailing list and set the defaults. mlist = MailList() try: # Use a fake list admin password; Mailman will never be # administered from its web u/i. Nor will the mailing list # require an owner that's different from the site owner. Also by # default, only English is supported. try: mlist.Create(team_name, mm_cfg.SITE_LIST_OWNER, " no password ") # Additional hard coded list defaults. # - Personalize regular delivery so that we can VERP these. # - Turn off RFC 2369 headers; we'll do them differently # - enable $-string substitutions in headers/footers mlist.personalize = 1 mlist.include_rfc2369_headers = False mlist.use_dollar_strings = True mlist.held_message_ids = {} mlist.Save() # Now create the archive directory for MHonArc. path = os.path.join(mm_cfg.VAR_PREFIX, "mhonarc", team_name) os.makedirs(path) # We have to use a bare except here because of the legacy string # exceptions that Mailman can raise. except: log_exception("List creation error for team: %s", team_name) return False else: return True finally: mlist.Unlock()
def process(res, args): mlist = res.mlist if args: res.results.append(_('Usage:')) res.results.append(gethelp(mlist)) return STOP hostname = mlist.host_name res.results.append(_('Public mailing lists at %(hostname)s:')) lists = Utils.list_names() lists.sort() i = 1 for listname in lists: if listname == mlist.internal_name(): xlist = mlist else: xlist = MailList(listname, lock=0) # We can mention this list if you already know about it if not xlist.advertised and xlist is not mlist: continue # Skip the list if it isn't in the same virtual domain. BAW: should a # message to the site list include everything regardless of domain? if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ xlist.host_name != mlist.host_name: continue realname = xlist.real_name description = xlist.description or _('n/a') requestaddr = xlist.GetRequestEmail() if i > 1: res.results.append('') res.results.append(_('%(i)3d. List name: %(realname)s')) res.results.append(_(' Description: %(description)s')) res.results.append(_(' Requests to: %(requestaddr)s')) i += 1
def do_include(mlist, msg, msgdata, recips): # regular_include_lists are the other mailing lists on this mailman # installation whose members are included in the regular (non-digest) # delivery if those list addresses don't appear in To: or Cc: headers. if not mlist.regular_include_lists: return recips recips = set(recips) destinations = email.Utils.getaddresses(msg.get_all('to', []) + msg.get_all('cc', [])) destinations = [y.lower() for x,y in destinations] for listname in mlist.regular_include_lists: listname = listname.lower() if listname in destinations: continue listlhs, hostname = listname.split('@') if listlhs == mlist.internal_name(): syslog('error', 'Include list %s is a self reference.', listname) continue try: slist = MailList(listlhs, lock=False) except MMUnknownListError: syslog('error', 'Include list %s not found.', listname) continue if not mm_cfg.ALLOW_CROSS_DOMAIN_SIBLING \ and slist.host_name != hostname: syslog('error', 'Include list %s is not in the same domain.', listname) continue srecips = set([slist.getMemberCPAddress(m) for m in slist.getRegularMemberKeys() if slist.getDeliveryStatus(m) == ENABLED]) recips |= srecips return list(recips)
def _create(self, team_name): """Create a new mailing list.""" # Create the mailing list and set the defaults. mlist = MailList() try: # Use a fake list admin password; Mailman will never be # administered from its web u/i. Nor will the mailing list # require an owner that's different from the site owner. Also by # default, only English is supported. try: mlist.Create(team_name, mm_cfg.SITE_LIST_OWNER, ' no password ') # Additional hard coded list defaults. # - Personalize regular delivery so that we can VERP these. # - Turn off RFC 2369 headers; we'll do them differently # - enable $-string substitutions in headers/footers mlist.personalize = 1 mlist.include_rfc2369_headers = False mlist.use_dollar_strings = True mlist.held_message_ids = {} mlist.Save() # Now create the archive directory for MHonArc. path = os.path.join(mm_cfg.VAR_PREFIX, 'mhonarc', team_name) os.makedirs(path) # We have to use a bare except here because of the legacy string # exceptions that Mailman can raise. except: log_exception('List creation error for team: %s', team_name) return False else: return True finally: mlist.Unlock()
def _apply_list_defaults(self, team_name, list_defaults): """Apply mailing list defaults and tie the new list into the MTA.""" mlist = MailList(team_name) try: for key, value in list_defaults.items(): setattr(mlist, key, value) # Do MTA specific creation steps. if mm_cfg.MTA: modname = 'Mailman.MTA.' + mm_cfg.MTA __import__(modname) sys.modules[modname].create(mlist, quiet=True) mlist.Save() finally: mlist.Unlock()
def getListInfo(self): """Return a list of 4-tuples of Mailman mailing list info.""" list_info = [] for list_name in sorted(list_names()): if list_name == mm_cfg.MAILMAN_SITE_LIST: continue mailing_list = MailList(list_name, lock=False) list_address = mailing_list.getListAddress() if self.naked_email_address_set.getByEmail(list_address) is None: email = '%s not found' % list_address else: email = list_address list_info.append( (mailing_list.internal_name(), mailing_list.host_name, mailing_list.web_page_url, email)) return list_info
def getListInfo(self): """Return a list of 4-tuples of Mailman mailing list info.""" list_info = [] for list_name in sorted(list_names()): if list_name == mm_cfg.MAILMAN_SITE_LIST: continue mailing_list = MailList(list_name, lock=False) list_address = mailing_list.getListAddress() if self.naked_email_address_set.getByEmail(list_address) is None: email = '%s not found' % list_address else: email = list_address list_info.append( (mailing_list.internal_name(), mailing_list.host_name, mailing_list.web_page_url, email)) return list_info
def main(): for x in list_names(): ml = MailList(x, True) try: changed = False for o in ml.owner: if not o in ALLOWED_OWNERS: print 'Removing %s from %s' % (o, x) ml.owner.remove(o) changed = True if not ml.owner: ml.owner.append(ALLOWED_OWNERS[0]) changed = True if changed: ml.Save() finally: ml.Unlock()
def main(): for x in list_names(): ml = MailList(x, True) try: changed = False if ml.host_name != settings.LISTS_MAILDOMAIN: print 'Updating host_name of %s' % x ml.host_name = settings.LISTS_MAILDOMAIN changed = True if ml.from_is_list != 1: print 'Updating from_is_list of %s' % x ml.from_is_list = 1 changed = True if changed: print 'Saving %s' % x ml.Save() finally: ml.Unlock()
def check_geinteresseerden(): print "GEINTERESSEERDEN" es = frozenset( map(lambda m: m.email.lower(), OldKnGroup.objects.get(name=MEMBER_GROUP).user_set.all())) ml = MailList('geinteresseerden', False) for m in ml.members: if m.lower() in es: print "%s in geinteresseerden" % m
def _resynchronize(self, actions, statuses): """Process resynchronization actions. actions is a sequence of 2-tuples specifying what needs to be resynchronized. The tuple is of the form (listname, current-status). statuses is a dictionary mapping team names to one of the strings 'success' or 'failure'. """ syslog('xmlrpc', 'resynchronizing: %s', COMMASPACE.join(sorted(name for (name, status) in actions))) for name, status in actions: # There's no way to really know whether the original action # succeeded or not, however, it's unlikely that an action would # fail leaving the mailing list in a usable state. Therefore, if # the list is loadable and lockable, we'll say it succeeded. try: mlist = MailList(name) except Errors.MMUnknownListError: # The list doesn't exist on the Mailman side, so if its status # is CONSTRUCTING, we can create it now. if status == 'constructing': if self._create(name): statuses[name] = ('resynchronize', 'success') else: statuses[name] = ('resynchronize', 'failure') else: # Any other condition leading to an unknown list is a # failure state. statuses[name] = ('resynchronize', 'failure') except: # Any other exception is also a failure. statuses[name] = ('resynchronize', 'failure') log_exception('Mailing list does not load: %s', name) else: # The list loaded just fine, so it successfully # resynchronized. Be sure to unlock it! mlist.Unlock() statuses[name] = ('resynchronize', 'success')
def _modify(self, actions, statuses): """Process mailing list modification actions. actions is a sequence of (team_name, modifications) tuples where the team_name is the name of the mailing list to create and modifications is a dictionary of values to set on the mailing list. statuses is a dictionary mapping team names to one of the strings 'success' or 'failure'. """ for team_name, modifications in actions: # First, validate the modification keywords. list_settings = {} for key in attrmap: if key in modifications: list_settings[attrmap[key]] = modifications[key] del modifications[key] if modifications: statuses[team_name] = ('modify', 'failure') syslog('xmlrpc', 'Unexpected modify settings: %s', COMMASPACE.join(modifications)) continue try: mlist = MailList(team_name) try: for key, value in list_settings.items(): setattr(mlist, key, value) mlist.Save() finally: mlist.Unlock() # We have to use a bare except here because of the legacy string # exceptions that Mailman can raise. except: log_exception('List modification error for team: %s', team_name) statuses[team_name] = ('modify', 'failure') else: statuses[team_name] = ('modify', 'success')
def main(): url = 'https://%s/mailman/' % settings.MAILDOMAIN for x in list_names(): ml = MailList(x, True) try: changed = False if ml.host_name != settings.LISTS_MAILDOMAIN: print 'Updating host_name of %s (was %s)' % (x, ml.host_name) ml.host_name = settings.LISTS_MAILDOMAIN changed = True if ml.from_is_list != 1: print 'Updating from_is_list of %s' % x ml.from_is_list = 1 changed = True if ml.web_page_url != url: print 'Updating url_host of %s (was %s)' % (x, ml.web_page_url) ml.web_page_url = url changed = True # if changed: # print 'Saving %s' % x # ml.Save() finally: ml.Unlock()
def matches_p(sender, nonmembers, listname): # First strip out all the regular expressions and listnames plainaddrs = [ addr for addr in nonmembers if not (addr.startswith('^') or addr.startswith('@')) ] addrdict = Utils.List2Dict(plainaddrs, foldcase=1) if addrdict.has_key(sender): return 1 # Now do the regular expression matches for are in nonmembers: if are.startswith('^'): try: cre = re.compile(are, re.IGNORECASE) except re.error: continue if cre.search(sender): return 1 elif are.startswith('@'): # XXX Needs to be reviewed for list@domain names. try: mname = are[1:].lower().strip() if mname == listname: # don't reference your own list syslog('error', '*_these_nonmembers in %s references own list', listname) else: mother = MailList(mname, lock=0) if mother.isMember(sender): return 1 except Errors.MMUnknownListError: syslog( 'error', '*_these_nonmembers in %s references non-existent list %s', listname, mname) return 0
def dump(self, listnames, password_scheme): print >> self._fp, '<?xml version="1.0" encoding="UTF-8"?>' self._push_element( 'mailman', **{ 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation': 'ssi-1.0.xsd', }) for listname in sorted(listnames): try: mlist = MailList(listname, lock=False) except Errors.MMUnknownListError: print >> sys.stderr, _('No such list: %(listname)s') continue self._dump_list(mlist, password_scheme) self._pop_element('mailman')
def _deactivate(self, actions, statuses): """Process mailing list deactivation actions. actions is a sequence of team names for the mailing lists to deactivate. statuses is a dictionary mapping team names to one of the strings 'success' or 'failure'. """ for team_name in actions: try: mlist = MailList(team_name, lock=False) if mm_cfg.MTA: modname = 'Mailman.MTA.' + mm_cfg.MTA __import__(modname) sys.modules[modname].remove(mlist, quiet=True) # The archives are always persistent, so all we need to do to # deactivate a list is to delete the 'lists/team_name' # directory. However, in order to support easy reactivation, # and to provide a backup in case of error, we create a gzip'd # tarball of the list directory. lists_dir = os.path.join(mm_cfg.VAR_PREFIX, 'lists') # To make reactivation easier, we temporarily cd to the # $var/lists directory and make the tarball from there. old_cwd = os.getcwd() # XXX BarryWarsaw 2007-08-02: Should we watch out for # collisions on the tar file name? This can only happen if # the team is resurrected but the old archived tarball backup # wasn't removed. tgz_file_name = os.path.join(mm_cfg.VAR_PREFIX, 'backups', team_name + '.tgz') tgz_file = tarfile.open(tgz_file_name, 'w:gz') try: os.chdir(lists_dir) # .add() works recursively by default. tgz_file.add(team_name) # Now delete the list's directory. shutil.rmtree(team_name) finally: tgz_file.close() os.chdir(old_cwd) # We have to use a bare except here because of the legacy string # exceptions that Mailman can raise. except: log_exception('List deletion error for team: %s', team_name) statuses[team_name] = ('deactivate', 'failure') else: statuses[team_name] = ('deactivate', 'success')
def wrapper(context, request): params = {} try: body = request.json_body except: raise HTTPBadRequest('Not JSON', body_template='${detail}') params['mail'] = body.get('mail') # TODO: check if mail is a valid mail if params['mail'] is None: raise HTTPBadRequest('Missing e-mail', body_template='${detail}') try: params['liste'] = MailList(request.matchdict.get('liste'), lock=1) except Errors.MMUnknownListError: raise HTTPNotFound('Unknown list') return func(request, params)
def main(): for x in list_names(): ml = MailList(x, True) try: changed = False if ml.host_name != settings.LISTS_MAILDOMAIN: print 'Updating host_name of %s' % x ml.host_name = settings.LISTS_MAILDOMAIN changed = True if ml.from_is_list != 1: print 'Updating from_is_list of %s' % x ml.from_is_list = 1 changed = True if changed: print 'Saving %s' % x ml.Save() finally: ml.Unlock()
def SendHostileSubscriptionNotice(self, listname, address): # Some one was invited to one list but tried to confirm to a different # list. We inform both list owners of the bogosity, but be careful # not to reveal too much information. selfname = self.internal_name() syslog('mischief', '%s was invited to %s but confirmed to %s', address, listname, selfname) # First send a notice to the attacked list msg = Message.OwnerNotification( self, _('Hostile subscription attempt detected'), Utils.wrap( _("""%(address)s was invited to a different mailing list, but in a deliberate malicious attempt they tried to confirm the invitation to your list. We just thought you'd like to know. No further action by you is required."""))) msg.send(self) # Now send a notice to the invitee list try: # Avoid import loops from Mailman.MailList import MailList mlist = MailList(listname, lock=False) except Errors.MMListError: # Oh well return otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: msg = Message.OwnerNotification( mlist, _('Hostile subscription attempt detected'), Utils.wrap( _("""You invited %(address)s to your list, but in a deliberate malicious attempt, they tried to confirm the invitation to a different list. We just thought you'd like to know. No further action by you is required."""))) msg.send(mlist) finally: i18n.set_translation(otrans)
def main(): url = 'https://%s/mailman/' % settings.MAILDOMAIN for x in list_names(): ml = MailList(x, True) try: changed = False if ml.host_name != settings.LISTS_MAILDOMAIN: print('Updating host_name of %s (was %s)' % (x, ml.host_name)) ml.host_name = settings.LISTS_MAILDOMAIN changed = True if ml.from_is_list != 1: print('Updating from_is_list of %s' % x) ml.from_is_list = 1 changed = True if ml.web_page_url != url: print('Updating url_host of %s (was %s)' % (x, ml.web_page_url)) ml.web_page_url = url changed = True if changed: print('Saving %s' % x) # ml.Save() finally: ml.Unlock()
def _update_list_subscriptions(self, list_name, subscription_info): """Update the subscription information for a single mailing list. :param list_name: The name of the mailing list to update. :type list_name: string :param subscription_info: The mailing list's new subscription information. :type subscription_info: a list of 4-tuples containing the address, real name, flags, and status of each person in the list's subscribers. """ ## syslog('xmlrpc', '%s subinfo: %s', list_name, subscription_info) # Start with an unlocked list. mlist = MailList(list_name, lock=False) # Create a mapping of email address to the member's real name, # flags, and status. Note that flags is currently unused. member_map = dict( (address, (realname, flags, status)) for address, realname, flags, status in subscription_info ) # In Mailman parlance, the 'member key' is the lower-cased # address. We need a mapping from the member key to # case-preserved address for all future members of the list. key_to_case_preserved = dict((address.lower(), address) for address in member_map) # The following modifications to membership may be made: # - Current members whose address is changing case # - New members who need to be added to the mailing list # - Old members who need to be removed from the mailing list # - Current members whose settings are being changed # # Start by getting the case-folded membership sets of all current # and future members. current_members = set(mlist.getMembers()) future_members = set(key_to_case_preserved) # Additions are all those addresses in future_members who are not # in current_members. additions = future_members - current_members # Deletions are all those addresses in current_members who are not # in future_members. deletions = current_members - future_members # Any address in both current and future members is either # changing case, updating settings, or both. updates = current_members & future_members # If there's nothing to do, just skip this list. if not additions and not deletions and not updates: # Why did we get here? This list should not have shown up in # getMembershipInformation(). syslog("xmlrpc", "Strange subscription information for list: %s", list_name) return # Lock the list and make the modifications. Don't worry if the list # couldn't be locked, we'll just log that and try again the next time # through the loop. Eventually any existing lock will expire anyway. try: mlist.Lock(2) except TimeOutError: syslog("xmlrpc", "Could not lock the list to update subscriptions: %s", list_name) return try: # Handle additions first. if len(additions) > 0: syslog("xmlrpc", "Adding to %s: %s", list_name, additions) for address in additions: # When adding the new member, be sure to use the # case-preserved email address. original_address = key_to_case_preserved[address] realname, flags, status = member_map[original_address] mlist.addNewMember(original_address, realname=realname) mlist.setDeliveryStatus(original_address, status) # Handle deletions next. if len(deletions) > 0: syslog("xmlrpc", "Removing from %s: %s", list_name, deletions) for address in deletions: mlist.removeMember(address) # Updates can be either a settings update, a change in the # case of the subscribed address, or both. found_updates = [] for address in updates: # See if the case is changing. current_address = mlist.getMemberCPAddress(address) future_address = key_to_case_preserved[address] if current_address != future_address: mlist.changeMemberAddress(address, future_address) found_updates.append("%s -> %s" % (address, future_address)) # flags are ignored for now. realname, flags, status = member_map[future_address] if realname != mlist.getMemberName(address): mlist.setMemberName(address, realname) found_updates.append("%s new name: %s" % (address, realname)) if status != mlist.getDeliveryStatus(address): mlist.setDeliveryStatus(address, status) found_updates.append("%s new status: %s" % (address, status)) if len(found_updates) > 0: syslog("xmlrpc", "Membership updates for %s: %s", list_name, found_updates) # We're done, so flush the changes for this mailing list. mlist.Save() finally: mlist.Unlock()
def registerBounce(self, member, msg, weight=1.0, day=None, sibling=False): if not self.isMember(member): # check regular_include_lists, only one level if not self.regular_include_lists or sibling: return from Mailman.MailList import MailList for listaddr in self.regular_include_lists: listname, hostname = listaddr.split('@') listname = listname.lower() if listname == self.internal_name(): syslog('error', 'Bouncer: %s: Include list self reference', listname) continue try: siblist = None try: siblist = MailList(listname) except MMUnknownListError: syslog('error', 'Bouncer: %s: Include list "%s" not found.', self.real_name, listname) continue siblist.registerBounce(member, msg, weight, day, sibling=True) siblist.Save() finally: if siblist and siblist.Locked(): siblist.Unlock() return info = self.getBounceInfo(member) first_today = True if day is None: # Use today's date day = time.localtime()[:3] if not isinstance(info, _BounceInfo): # This is the first bounce we've seen from this member info = _BounceInfo(member, weight, day, self.bounce_you_are_disabled_warnings) # setBounceInfo is now called below after check phase. syslog('bounce', '%s: %s bounce score: %s', self.internal_name(), member, info.score) # Continue to the check phase below elif self.getDeliveryStatus(member) <> MemberAdaptor.ENABLED: # The user is already disabled, so we can just ignore subsequent # bounces. These are likely due to residual messages that were # sent before disabling the member, but took a while to bounce. syslog('bounce', '%s: %s residual bounce received', self.internal_name(), member) return elif info.date == day: # We've already scored any bounces for this day, so ignore it. first_today = False syslog('bounce', '%s: %s already scored a bounce for date %s', self.internal_name(), member, time.strftime('%d-%b-%Y', day + (0, 0, 0, 0, 1, 0))) # Continue to check phase below else: # See if this member's bounce information is stale. now = Utils.midnight(day) lastbounce = Utils.midnight(info.date) if lastbounce + self.bounce_info_stale_after < now: # Information is stale, so simply reset it info.reset(weight, day, self.bounce_you_are_disabled_warnings) syslog('bounce', '%s: %s has stale bounce info, resetting', self.internal_name(), member) else: # Nope, the information isn't stale, so add to the bounce # score and take any necessary action. info.score += weight info.date = day syslog('bounce', '%s: %s current bounce score: %s', self.internal_name(), member, info.score) # Continue to the check phase below # # Now that we've adjusted the bounce score for this bounce, let's # check to see if the disable-by-bounce threshold has been reached. if info.score >= self.bounce_score_threshold: if mm_cfg.VERP_PROBES: syslog('bounce', 'sending %s list probe to: %s (score %s >= %s)', self.internal_name(), member, info.score, self.bounce_score_threshold) self.sendProbe(member, msg) info.reset(0, info.date, info.noticesleft) else: self.disableBouncingMember(member, info, msg) elif self.bounce_notify_owner_on_bounce_increment and first_today: self.__sendAdminBounceNotice(member, msg, did=_('bounce score incremented')) # We've set/changed bounce info above. We now need to tell the # MemberAdaptor to set/update it. We do it here in case the # MemberAdaptor stores bounce info externally to the list object to # be sure updated information is stored, but we have to be sure the # member wasn't removed. if self.isMember(member): self.setBounceInfo(member, info)
elif opt in ('-n', '--dry-run'): dry_run_p = True verbose_p = True if len(args) < 1: pdie(1, C_('listname is required')) listname = args[0].lower().strip() num = int(args[1]) if num < 1: pdie(1, 'Number (>0) required') mlist = None try: try: mlist = MailList(listname, lock=True) except Errors.MMListError, e: pdie(2, C_('No such list "%(listname)s"\n%(e)s')) index = os.path.join(mlist.archive_dir(), 'pipermail.pck') if not os.path.isfile(index): if verbose_p: print("No archives", file=sys.stderr) return with open(index, 'r') as f: d = pickle.load(f) if not (len(d['archives']) > num): if verbose_p: print("No expirations", file=sys.stderr) return
def _addvirtual(mlist, fp): listname = mlist.internal_name() fieldsz = len(listname) + len('-unsubscribe') hostname = mlist.host_name # Set up the mailman-loop address loopaddr = Utils.get_site_email(mlist.host_name, extra='loop') loopdest = Utils.ParseEmail(loopaddr)[0] # And the site list posting address. siteaddr = Utils.get_site_email(mlist.host_name) sitedest = Utils.ParseEmail(siteaddr)[0] # And the site list -owner address. siteowneraddr = Utils.get_site_email(mlist.host_name, extra='owner') siteownerdest = Utils.ParseEmail(siteowneraddr)[0] if mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN: loopdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN sitedest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN siteownerdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN # If the site list's host_name is a virtual domain, adding the list and # owner addresses to the SITE ADDRESSES will duplicate the entries in the # stanza for the list. Postfix doesn't like dups so we try to comment them # here, but only for the actual site list domain. if (MailList(mm_cfg.MAILMAN_SITE_LIST, lock=False).host_name.lower() == hostname.lower()): siteaddr = '#' + siteaddr siteowneraddr = '#' + siteowneraddr # Seek to the end of the text file, but if it's empty write the standard # disclaimer, and the loop catch address and site address. fp.seek(0, 2) if not fp.tell(): print >> fp, """\ # This file is generated by Mailman, and is kept in sync with the binary hash # file virtual-mailman.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you # know what you're doing, and can keep the two files properly in sync. If you # screw it up, you're on your own. # # Note that you should already have this virtual domain set up properly in # your Postfix installation. See README.POSTFIX for details. # LOOP ADDRESSES START %s\t%s # LOOP ADDRESSES END # We also add the site list address in each virtual domain as that address # is exposed on admin and listinfo overviews, and we add the site list-owner # address as it is exposed in the list created email notice. # SITE ADDRESSES START %s\t%s %s\t%s # SITE ADDRESSES END """ % (loopaddr, loopdest, siteaddr, sitedest, siteowneraddr, siteownerdest) # The text file entries get a little extra info print >> fp, '# STANZA START:', listname print >> fp, '# CREATED:', time.ctime(time.time()) # Now add all the standard alias entries for k, v in makealiases(listname): fqdnaddr = '%s@%s' % (k, hostname) if mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN: localaddr = '%s@%s' % (k, mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN) else: localaddr = k # Format the text file nicely print >> fp, fqdnaddr, ((fieldsz - len(k)) * ' '), localaddr # Finish the text file stanza print >> fp, '# STANZA END:', listname print >> fp
def registerBounce(self, member, msg, weight=1.0, day=None, sibling=False): if not self.isMember(member): # check regular_include_lists, only one level if not self.regular_include_lists or sibling: return from Mailman.MailList import MailList for listaddr in self.regular_include_lists: listname, hostname = listaddr.split("@") listname = listname.lower() if listname == self.internal_name(): syslog("error", "Bouncer: %s: Include list self reference", listname) continue try: siblist = None try: siblist = MailList(listname) except MMUnknownListError: syslog("error", 'Bouncer: %s: Include list "%s" not found.', self.real_name, listname) continue siblist.registerBounce(member, msg, weight, day, sibling=True) siblist.Save() finally: if siblist and siblist.Locked(): siblist.Unlock() return info = self.getBounceInfo(member) if day is None: # Use today's date day = time.localtime()[:3] if not isinstance(info, _BounceInfo): # This is the first bounce we've seen from this member info = _BounceInfo(member, weight, day, self.bounce_you_are_disabled_warnings) # setBounceInfo is now called below after check phase. syslog("bounce", "%s: %s bounce score: %s", self.internal_name(), member, info.score) # Continue to the check phase below elif self.getDeliveryStatus(member) <> MemberAdaptor.ENABLED: # The user is already disabled, so we can just ignore subsequent # bounces. These are likely due to residual messages that were # sent before disabling the member, but took a while to bounce. syslog("bounce", "%s: %s residual bounce received", self.internal_name(), member) return elif info.date == day: # We've already scored any bounces for this day, so ignore it. syslog( "bounce", "%s: %s already scored a bounce for date %s", self.internal_name(), member, time.strftime("%d-%b-%Y", day + (0, 0, 0, 0, 1, 0)), ) # Continue to check phase below else: # See if this member's bounce information is stale. now = Utils.midnight(day) lastbounce = Utils.midnight(info.date) if lastbounce + self.bounce_info_stale_after < now: # Information is stale, so simply reset it info.reset(weight, day, self.bounce_you_are_disabled_warnings) syslog("bounce", "%s: %s has stale bounce info, resetting", self.internal_name(), member) else: # Nope, the information isn't stale, so add to the bounce # score and take any necessary action. info.score += weight info.date = day syslog("bounce", "%s: %s current bounce score: %s", self.internal_name(), member, info.score) # Continue to the check phase below # # Now that we've adjusted the bounce score for this bounce, let's # check to see if the disable-by-bounce threshold has been reached. if info.score >= self.bounce_score_threshold: if mm_cfg.VERP_PROBES: syslog( "bounce", "sending %s list probe to: %s (score %s >= %s)", self.internal_name(), member, info.score, self.bounce_score_threshold, ) self.sendProbe(member, msg) info.reset(0, info.date, info.noticesleft) else: self.disableBouncingMember(member, info, msg) # We've set/changed bounce info above. We now need to tell the # MemberAdaptor to set/update it. We do it here in case the # MemberAdaptor stores bounce info externally to the list object to # be sure updated information is stored, but we have to be sure the # member wasn't removed. if self.isMember(member): self.setBounceInfo(member, info)
def _check_for_virtual_loopaddr(mlist, filename): loopaddr = Utils.get_site_email(mlist.host_name, extra='loop') loopdest = Utils.ParseEmail(loopaddr)[0] siteaddr = Utils.get_site_email(mlist.host_name) sitedest = Utils.ParseEmail(siteaddr)[0] siteowneraddr = Utils.get_site_email(mlist.host_name, extra='owner') siteownerdest = Utils.ParseEmail(siteowneraddr)[0] if mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN: loopdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN sitedest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN siteownerdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN # If the site list's host_name is a virtual domain, adding the list and # owner addresses to the SITE ADDRESSES will duplicate the entries in the # stanza for the list. Postfix doesn't like dups so we try to comment them # here, but only for the actual site list domain. if (MailList(mm_cfg.MAILMAN_SITE_LIST, lock=False).host_name.lower() == mlist.host_name.lower()): siteaddr = '#' + siteaddr siteowneraddr = '#' + siteowneraddr infp = open(filename) omask = os.umask(007) try: outfp = open(filename + '.tmp', 'w') finally: os.umask(omask) try: # Find the start of the loop address block while True: line = infp.readline() if not line: break outfp.write(line) if line.startswith('# LOOP ADDRESSES START'): break # Now see if our domain has already been written while True: line = infp.readline() if not line: break if line.startswith('# LOOP ADDRESSES END'): # It hasn't print >> outfp, '%s\t%s' % (loopaddr, loopdest) outfp.write(line) break elif line.startswith(loopaddr): # We just found it outfp.write(line) break else: # This isn't our loop address, so spit it out and continue outfp.write(line) # Now do it all again for the site list address. It must follow the # loop addresses. while True: line = infp.readline() if not line: break outfp.write(line) if line.startswith('# SITE ADDRESSES START'): break # Now see if our domain has already been written while True: line = infp.readline() if not line: break if line.startswith('# SITE ADDRESSES END'): # It hasn't print >> outfp, '%s\t%s' % (siteaddr, sitedest) print >> outfp, '%s\t%s' % (siteowneraddr, siteownerdest) outfp.write(line) break elif line.startswith(siteaddr) or line.startswith('#' + siteaddr): # We just found it outfp.write(line) break else: # This isn't our loop address, so spit it out and continue outfp.write(line) outfp.writelines(infp.readlines()) finally: infp.close() outfp.close() os.rename(filename + '.tmp', filename)
#!/usr/bin/python2 import imp import json import sys paths = imp.load_source('paths', '/usr/lib/mailman/bin/paths.py') from Mailman.MailList import MailList from Mailman.Utils import list_names json.dump( { 'ansible_facts': { 'mailman': { 'lists': [ MailList(name, lock=0).internal_name() for name in list_names() ], }, }, }, sys.stdout, indent=2)
def fixHostnames(self): """Fix up the host names in Mailman and the LP database.""" # These can't be done at module global scope. from Mailman import Utils from Mailman import mm_cfg from Mailman.MailList import MailList # Grab a couple of useful components. email_address_set = getUtility(IEmailAddressSet) mailing_list_set = getUtility(IMailingListSet) # Clean things up per mailing list. for list_name in Utils.list_names(): # Skip the site list. if list_name == mm_cfg.MAILMAN_SITE_LIST: continue # The first thing to clean up is the mailing list pickles. There # are things like host names in some attributes that need to be # converted. The following opens a locked list. mailing_list = MailList(list_name) try: mailing_list.host_name = mm_cfg.DEFAULT_EMAIL_HOST mailing_list.web_page_url = ( mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST) mailing_list.Save() finally: mailing_list.Unlock() # Patch up the email address for the list in the Launchpad # database. lp_mailing_list = mailing_list_set.get(list_name) if lp_mailing_list is None: # We found a mailing list in Mailman that does not exist in # the Launchpad database. This can happen if we rsync'd the # Mailman directories after the lists were created, but we # copied the LP database /before/ the lists were created. # If we don't delete the Mailman lists, we won't be able to # create the mailing lists on staging. self.logger.error('No LP mailing list for: %s', list_name) self.deleteMailmanList(list_name) continue # Clean up the team email addresses corresponding to their mailing # lists. Note that teams can have two email addresses if they # have a different contact address. team = getUtility(IPersonSet).getByName(list_name) mlist_addresses = email_address_set.getByPerson(team) if mlist_addresses.count() == 0: self.logger.error('No LP email address for: %s', list_name) else: # Teams can have both a mailing list and a contact address. old_address = '%s@%s' % (list_name, self.options.hostname) for email_address in mlist_addresses: if email_address.email == old_address: new_address = lp_mailing_list.address removeSecurityProxy(email_address).email = new_address self.logger.info('%s -> %s', old_address, new_address) break else: self.logger.error('No change to LP email address for: %s', list_name)
def _check_held_messages(self): """See if any held messages have been accepted or rejected.""" try: dispositions = self._proxy.getMessageDispositions() except (xmlrpclib.ProtocolError, socket.error) as error: log_exception('Cannot talk to Launchpad:\n%s', error) return except xmlrpclib.Fault as error: log_exception('Launchpad exception: %s', error) return if dispositions: syslog('xmlrpc', 'Received dispositions for these message-ids: %s', COMMASPACE.join(dispositions)) else: return # For each message that has been acted upon in Launchpad, handle the # message in here in Mailman. We need to resort the dispositions so # that we can handle all of them for a particular mailing list at the # same time. by_list = {} for message_id, (team_name, action) in dispositions.items(): accepts, declines, discards = by_list.setdefault( team_name, ([], [], [])) if action == 'accept': accepts.append(message_id) elif action == 'decline': declines.append(message_id) elif action == 'discard': discards.append(message_id) else: syslog('xmlrpc', 'Skipping invalid disposition "%s" for message-id: %s', action, message_id) # Now cycle through the dispositions for every mailing list. for team_name in by_list: try: mlist = MailList(team_name) except Errors.MMUnknownListError: log_exception('Skipping dispositions for unknown list: %s', team_name) continue try: accepts, declines, discards = by_list[team_name] for message_id in accepts: request_id = mlist.held_message_ids.pop(message_id, None) if request_id is None: syslog('xmlrpc', 'Missing accepted message-id: %s', message_id) else: mlist.HandleRequest(request_id, mm_cfg.APPROVE) syslog('xmlrpc', 'Approved: %s', message_id) for message_id in declines: request_id = mlist.held_message_ids.pop(message_id, None) if request_id is None: syslog('xmlrpc', 'Missing declined message-id: %s', message_id) else: mlist.HandleRequest(request_id, mm_cfg.REJECT) syslog('xmlrpc', 'Rejected: %s', message_id) for message_id in discards: request_id = mlist.held_message_ids.pop(message_id, None) if request_id is None: syslog('xmlrpc', 'Missing declined message-id: %s', message_id) else: mlist.HandleRequest(request_id, mm_cfg.DISCARD) syslog('xmlrpc', 'Discarded: %s', message_id) mlist.Save() finally: mlist.Unlock()
def _update_list_subscriptions(self, list_name, subscription_info): """Update the subscription information for a single mailing list. :param list_name: The name of the mailing list to update. :type list_name: string :param subscription_info: The mailing list's new subscription information. :type subscription_info: a list of 4-tuples containing the address, real name, flags, and status of each person in the list's subscribers. """ ## syslog('xmlrpc', '%s subinfo: %s', list_name, subscription_info) # Start with an unlocked list. mlist = MailList(list_name, lock=False) # Create a mapping of email address to the member's real name, # flags, and status. Note that flags is currently unused. member_map = dict( (address, (realname, flags, status)) for address, realname, flags, status in subscription_info) # In Mailman parlance, the 'member key' is the lower-cased # address. We need a mapping from the member key to # case-preserved address for all future members of the list. key_to_case_preserved = dict( (address.lower(), address) for address in member_map) # The following modifications to membership may be made: # - Current members whose address is changing case # - New members who need to be added to the mailing list # - Old members who need to be removed from the mailing list # - Current members whose settings are being changed # # Start by getting the case-folded membership sets of all current # and future members. current_members = set(mlist.getMembers()) future_members = set(key_to_case_preserved) # Additions are all those addresses in future_members who are not # in current_members. additions = future_members - current_members # Deletions are all those addresses in current_members who are not # in future_members. deletions = current_members - future_members # Any address in both current and future members is either # changing case, updating settings, or both. updates = current_members & future_members # If there's nothing to do, just skip this list. if not additions and not deletions and not updates: # Why did we get here? This list should not have shown up in # getMembershipInformation(). syslog('xmlrpc', 'Strange subscription information for list: %s', list_name) return # Lock the list and make the modifications. Don't worry if the list # couldn't be locked, we'll just log that and try again the next time # through the loop. Eventually any existing lock will expire anyway. try: mlist.Lock(2) except TimeOutError: syslog('xmlrpc', 'Could not lock the list to update subscriptions: %s', list_name) return try: # Handle additions first. if len(additions) > 0: syslog('xmlrpc', 'Adding to %s: %s', list_name, additions) for address in additions: # When adding the new member, be sure to use the # case-preserved email address. original_address = key_to_case_preserved[address] realname, flags, status = member_map[original_address] mlist.addNewMember(original_address, realname=realname) mlist.setDeliveryStatus(original_address, status) # Handle deletions next. if len(deletions) > 0: syslog('xmlrpc', 'Removing from %s: %s', list_name, deletions) for address in deletions: mlist.removeMember(address) # Updates can be either a settings update, a change in the # case of the subscribed address, or both. found_updates = [] for address in updates: # See if the case is changing. current_address = mlist.getMemberCPAddress(address) future_address = key_to_case_preserved[address] if current_address != future_address: mlist.changeMemberAddress(address, future_address) found_updates.append('%s -> %s' % (address, future_address)) # flags are ignored for now. realname, flags, status = member_map[future_address] if realname != mlist.getMemberName(address): mlist.setMemberName(address, realname) found_updates.append('%s new name: %s' % (address, realname)) if status != mlist.getDeliveryStatus(address): mlist.setDeliveryStatus(address, status) found_updates.append('%s new status: %s' % (address, status)) if len(found_updates) > 0: syslog('xmlrpc', 'Membership updates for %s: %s', list_name, found_updates) # We're done, so flush the changes for this mailing list. mlist.Save() finally: mlist.Unlock()