def _addlist(mlist, fp): # Set up the mailman-loop address loopaddr = Utils.ParseEmail(Utils.get_site_email(extra='loop'))[0] loopmbox = os.path.join(mm_cfg.DATA_DIR, 'owner-bounces.mbox') # Seek to the end of the text file, but if it's empty write the standard # disclaimer, and the loop catch address. fp.seek(0, 2) if not fp.tell(): print >> fp, """\ # This file is generated by Mailman, and is kept in sync with the # binary hash file aliases.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE # unless you know what you're doing, and can keep the two files properly # in sync. If you screw it up, you're on your own. """ print >> fp, '# The ultimate loop stopper address' print >> fp, '%s: %s' % (loopaddr, loopmbox) print >> fp # Bootstrapping. bin/genaliases must be run before any lists are created, # but if no lists exist yet then mlist is None. The whole point of the # exercise is to get the minimal aliases.db file into existance. if mlist is None: return listname = mlist.internal_name() fieldsz = len(listname) + len('-unsubscribe') # The text file entries get a little extra info print >> fp, '# STANZA START:', listname print >> fp, '# CREATED:', time.ctime(time.time()) # Now add all the standard alias entries for k, v in makealiases(listname): # Format the text file nicely print >> fp, k + ':', ((fieldsz - len(k)) * ' ') + v # Finish the text file stanza print >> fp, '# STANZA END:', listname print >> fp
def verp_probe(mlist, msg): bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) # Sadly not every MTA bounces VERP messages correctly, or consistently. # Fall back to Delivered-To: (Postfix), Envelope-To: (Exim) and # Apparently-To:, and then short-circuit if we still don't have anything # to work with. Note that there can be multiple Delivered-To: headers so # we need to search them all (and we don't worry about false positives for # forwarded email, because only one should match VERP_REGEXP). vals = [] for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'): vals.extend(msg.get_all(header, [])) for field in vals: to = parseaddr(field)[1] if not to: continue # empty header mo = re.search(mm_cfg.VERP_PROBE_REGEXP, to) if not mo: continue # no match of regexp try: if bmailbox <> mo.group('bounces'): continue # not a bounce to our list # Extract the token and see if there's an entry token = mo.group('token') data = mlist.pend_confirm(token, expunge=False) if data is not None: return token except IndexError: syslog( 'error', "VERP_PROBE_REGEXP doesn't yield the right match groups: %s", mm_cfg.VERP_PROBE_REGEXP) return None
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] if mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN: loopdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN # Seek to the end of the text file, but if it's empty write the standard # disclaimer, and the loop catch address. fp.seek(0, 2) if not fp.tell(): print >> fp, """\ # This file is generated by Mailman, and is kept in sync with the binary hash # file virtual-mailman.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you # know what you're doing, and can keep the two files properly in sync. If you # screw it up, you're on your own. # # Note that you should already have this virtual domain set up properly in # your Postfix installation. See README.POSTFIX for details. # LOOP ADDRESSES START %s\t%s # LOOP ADDRESSES END """ % (loopaddr, loopdest) # The text file entries get a little extra info print >> fp, '# STANZA START:', listname print >> fp, '# CREATED:', time.ctime(time.time()) # Now add all the standard alias entries for k, v in makealiases(listname): fqdnaddr = '%s@%s' % (k, hostname) 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 _check_for_virtual_loopaddr(mlist, filename): loopaddr = Utils.get_site_email(mlist.host_name, extra='loop') loopdest = Utils.ParseEmail(loopaddr)[0] 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) outfp.writelines(infp.readlines()) finally: infp.close() outfp.close() os.rename(filename + '.tmp', filename)
def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): for recip in msgdata['recips']: # We now need to stitch together the message with its header and # footer. If we're VERPIng, we have to calculate the envelope sender # for each recipient. Note that the list of recipients must be of # length 1. # # BAW: ezmlm includes the message number in the envelope, used when # sending a notification to the user telling her how many messages # they missed due to bouncing. Neat idea. msgdata['recips'] = [recip] # Make a copy of the message and decorate + delivery that msgcopy = copy.deepcopy(msg) Decorate.process(mlist, msgcopy, msgdata) # Calculate the envelope sender, which we may be VERPing if msgdata.get('verp'): bmailbox, bdomain = Utils.ParseEmail(envsender) rmailbox, rdomain = Utils.ParseEmail(recip) if rdomain is None: # The recipient address is not fully-qualified. We can't # deliver it to this person, nor can we craft a valid verp # header. I don't think there's much we can do except ignore # this recipient. syslog('smtp', 'Skipping VERP delivery to unqual recip: %s', recip) continue d = {'bounces': bmailbox, 'mailbox': rmailbox, 'host' : DOT.join(rdomain), } envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain)) if mlist.personalize == 2: # When fully personalizing, we want the To address to point to the # recipient, not to the mailing list del msgcopy['to'] name = None if mlist.isMember(recip): name = mlist.getMemberName(recip) if name: # Convert the name to an email-safe representation. If the # name is a byte string, convert it first to Unicode, given # the character set of the member's language, replacing bad # characters for which we can do nothing about. Once we have # the name as Unicode, we can create a Header instance for it # so that it's properly encoded for email transport. charset = Utils.GetCharSet(mlist.getMemberLanguage(recip)) if charset == 'us-ascii': # Since Header already tries both us-ascii and utf-8, # let's add something a bit more useful. charset = 'iso-8859-1' charset = Charset(charset) codec = charset.input_codec or 'ascii' if not isinstance(name, str): name = str(name, codec, 'replace') name = Header(name, charset).encode() msgcopy['To'] = formataddr((name, recip)) else: msgcopy['To'] = recip # We can flag the mail as a duplicate for each member, if they've # already received this message, as calculated by Message-ID. See # AvoidDuplicates.py for details. del msgcopy['x-mailman-copy'] if recip in msgdata.get('add-dup-header', {}): msgcopy['X-Mailman-Copy'] = 'yes' # If desired, add the RCPT_BASE64_HEADER_NAME header if len(mm_cfg.RCPT_BASE64_HEADER_NAME) > 0: del msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] = b64encode(recip) # For the final delivery stage, we can just bulk deliver to a party of # one. ;) bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn)
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)
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