def __sendAdminBounceNotice(self, member, msg): # BAW: This is a bit kludgey, but we're not providing as much # information in the new admin bounce notices as we used to (some of # it was of dubious value). However, we'll provide empty, strange, or # meaningless strings for the unused %()s fields so that the language # translators don't have to provide new templates. siteowner = Utils.get_site_email(self.host_name) text = Utils.maketext( "bounce.txt", { "listname": self.real_name, "addr": member, "negative": "", "did": _("disabled"), "but": "", "reenable": "", "owneraddr": siteowner, }, mlist=self, ) subject = _("Bounce action notification") umsg = Message.UserNotification(self.GetOwnerEmail(), siteowner, subject, lang=self.preferred_language) # BAW: Be sure you set the type before trying to attach, or you'll get # a MultipartConversionError. umsg.set_type("multipart/mixed") umsg.attach(MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language))) if isinstance(msg, StringType): umsg.attach(MIMEText(msg)) else: umsg.attach(MIMEMessage(msg)) umsg.send(self)
def __sendAdminBounceNotice(self, member, msg, did=_('disabled')): # BAW: This is a bit kludgey, but we're not providing as much # information in the new admin bounce notices as we used to (some of # it was of dubious value). However, we'll provide empty, strange, or # meaningless strings for the unused %()s fields so that the language # translators don't have to provide new templates. siteowner = Utils.get_site_email(self.host_name) text = Utils.maketext('bounce.txt', { 'listname': self.real_name, 'addr': member, 'negative': '', 'did': did, 'but': '', 'reenable': '', 'owneraddr': siteowner, }, mlist=self) subject = _('Bounce action notification') umsg = Message.UserNotification(self.GetOwnerEmail(), siteowner, subject, lang=self.preferred_language) # BAW: Be sure you set the type before trying to attach, or you'll get # a MultipartConversionError. umsg.set_type('multipart/mixed') umsg.attach( MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language))) if isinstance(msg, StringType): umsg.attach(MIMEText(msg)) else: umsg.attach(MIMEMessage(msg)) umsg.send(self)
def _addvirtual(mlist, fp): listname = mlist.internal_name() fieldsz = len(listname) + len('-unsubscribe') hostname = mlist.host_name # Set up the mailman-loop address loopaddr = Utils.get_site_email(mlist.host_name, extra='loop') loopdest = Utils.ParseEmail(loopaddr)[0] # Seek to the end of the text file, but if it's empty write the standard # disclaimer, and the loop catch address. fp.seek(0, 2) if not fp.tell(): print >> fp, """\ # This file is generated by Mailman, and is kept in sync with the binary hash # file virtual-mailman.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you # know what you're doing, and can keep the two files properly in sync. If you # screw it up, you're on your own. # # Note that you should already have this virtual domain set up properly in # your Postfix installation. See README.POSTFIX for details. # LOOP ADDRESSES START %s\t%s # LOOP ADDRESSES END """ % (loopaddr, loopdest) # The text file entries get a little extra info print >> fp, '# STANZA START:', listname print >> fp, '# CREATED:', time.ctime(time.time()) # Now add all the standard alias entries for k, v in makealiases(listname): fqdnaddr = '%s@%s' % (k, hostname) # Format the text file nicely print >> fp, fqdnaddr, ((fieldsz - len(k)) * ' '), k # Finish the text file stanza print >> fp, '# STANZA END:', listname print >> fp
def _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 _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 create(self, email): if self.exists: raise ListAlreadyExists langs = [mm_cfg.DEFAULT_SERVER_LANGUAGE] pw = Utils.MakeRandomPassword() pw_hashed = Utils.sha_new(pw).hexdigest() urlhost = mm_cfg.DEFAULT_URL_HOST host_name = mm_cfg.DEFAULT_EMAIL_HOST web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost # TODO: Add some atomicity. We should roll back changes using # a try/else if something (like MTA alias update) fails # before the function terminates. try: oldmask = os.umask(002) self.mlist.Create(self.name, email, pw_hashed, langs=langs, emailhost=host_name, urlhost=urlhost) self.mlist.preferred_language = langs[0] # Reply-To set to list address self.mlist.reply_goes_to_list = 2 self.mlist.reply_to_address = "%s@%s" % (self.list, self.domain) # Allow messages from listname@domain self.mlist.acceptable_aliases = "%s@%s\n" % (self.list, self.domain) self.mlist.subject_prefix = "[%s] " % (self.list) self.mlist.msg_footer = "" self.mlist.subscribe_policy = 2 # Confirm and approve self.mlist.max_message_size = 20480 # 20M self.mlist.Save() finally: os.umask(oldmask) self.mlist.Unlock() if mm_cfg.MTA: modname = 'Mailman.MTA.' + mm_cfg.MTA __import__(modname) sys.modules[modname].create(self.mlist) siteowner = Utils.get_site_email(self.mlist.host_name, 'owner') text = Utils.maketext( 'newlist.txt', {'listname' : self.name, 'password' : pw, 'admin_url' : self.mlist.GetScriptURL('admin', absolute=1), 'listinfo_url': self.mlist.GetScriptURL('listinfo', absolute=1), 'requestaddr' : self.mlist.GetRequestEmail(), 'siteowner' : siteowner, }, mlist=self.mlist) msg = Message.UserNotification(email, siteowner, 'Your new mailing list: %s' % self.name, text, self.mlist.preferred_language) msg.send(self.mlist)
def openidreg_overview(lang, msg=''): # Present the general listinfo overview hostname = Utils.get_domain() # Set up the document and assign it the correct language. The only one we # know about at the moment is the server's default. doc = Document() # doc.set_language(lang) doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) legend = _(" OpenID Registeration for Systers Mailing Lists") doc.SetTitle(legend) table = Table(border=0, width="100%") table.AddRow([Center(Header(2, legend))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, bgcolor=mm_cfg.WEB_HEADER_COLOR) # Skip any mailing lists that isn't advertised. if msg: greeting = FontAttr(msg, color="ff5060", size="+1") else: greeting = FontAttr(_('Welcome!'), size='+2') welcome = [greeting] mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() # set up some local variables adj = msg and _('right') or '' siteowner = Utils.get_site_email() welcome.extend( (_(''' This is the Systers OpenID registeration form . To enable your systers account fill in the following entries. <p>or Go back to the listinfo page if already using it '''), Link(Utils.ScriptURL('listinfo'), _('the mailing lists overview page')), _(''' <p>If you are having trouble using the lists, please contact '''), Link('mailto:' + siteowner, siteowner), '.<p>', FormatOpenIDLogin(), '<p>')) table.AddRow([apply(Container, welcome)]) table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) doc.AddItem(table) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format()
def 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 notify_owner (self): """Send an email to the owner of the list of successful creation.""" siteowner = Utils.get_site_email(self.ml.host_name, 'owner') text = Utils.maketext( 'newlist.txt', {'listname' : self.ln, 'password' : '', 'admin_url' : self.ml.GetScriptURL('admin', absolute=1), 'listinfo_url': self.ml.GetScriptURL('listinfo', absolute=1), 'requestaddr' : self.ml.GetRequestEmail(), 'siteowner' : siteowner, }, mlist=self.ml) msg = Message.UserNotification( self.owner, siteowner, 'Your new mailing list: %s' % self.ln, text, self.ml.preferred_language) msg.send(self.ml)
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 __init__(self, mlist, subject=None, text=None, tomoderators=1): recips = mlist.owner[:] if tomoderators: recips.extend(mlist.moderator) # We have to set the owner to the site's -bounces address, otherwise # we'll get a mail loop if an owner's address bounces. sender = Utils.get_site_email(mlist.host_name, 'bounces') lang = mlist.preferred_language UserNotification.__init__(self, recips, sender, subject, text, lang) # Hack the To header to look like it's going to the -owner address del self['to'] self['To'] = mlist.GetOwnerEmail() self._sender = sender # User notifications are normally sent with Precedence: bulk. This # is appropriate as they can be backscatter of rejected spam. # Owner notifications are not backscatter and are perhaps more # important than 'bulk' so give them Precedence: list by default. # (LP: #1313146) self['Precedence'] = 'list'
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 _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)
dir) title = _('Mailing list deletion results') doc.SetTitle(title) table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) if not problems: table.AddRow([ _('''You have successfully deleted the mailing list <b>%(listname)s</b>.''') ]) else: sitelist = Utils.get_site_email(mlist.host_name) table.AddRow([ _('''There were some problems deleting the mailing list <b>%(listname)s</b>. Contact your site administrator at %(sitelist)s for details.''') ]) doc.AddItem(table) doc.AddItem('<hr>') doc.AddItem( _('Return to the ') + Link(Utils.ScriptURL('listinfo'), _('general list overview')).Format()) doc.AddItem( _('<br>Return to the ') + Link(Utils.ScriptURL( 'admin'), _('administrative list overview')).Format()) doc.AddItem(MailmanLogo())
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)
class MaildirRunner(Runner): # This class is much different than most runners because it pulls files # of a different format than what scripts/post and friends leaves. The # files this runner reads are just single message files as dropped into # the directory by the MTA. This runner will read the file, and enqueue # it in the expected qfiles directory for normal processing. def __init__(self, slice=None, numslices=1): # Don't call the base class constructor, but build enough of the # underlying attributes to use the base class's implementation. self._stop = 0 self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new') self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur') self._parser = Parser(Message) def _oneloop(self): # Refresh this each time through the list. BAW: could be too # expensive. listnames = Utils.list_names() # Cruise through all the files currently in the new/ directory try: files = os.listdir(self._dir) except OSError, e: if e.errno <> errno.ENOENT: raise # Nothing's been delivered yet return 0 for file in files: srcname = os.path.join(self._dir, file) dstname = os.path.join(self._cur, file + ':1,P') xdstname = os.path.join(self._cur, file + ':1,X') try: os.rename(srcname, dstname) except OSError, e: if e.errno == errno.ENOENT: # Some other MaildirRunner beat us to it continue syslog('error', 'Could not rename maildir file: %s', srcname) raise # Now open, read, parse, and enqueue this message try: fp = open(dstname) try: msg = self._parser.parse(fp) finally: fp.close() # Now we need to figure out which queue of which list this # message was destined for. See verp_bounce() in # BounceRunner.py for why we do things this way. vals = [] for header in ('delivered-to', 'envelope-to', 'apparently-to'): vals.extend(msg.get_all(header, [])) for field in vals: to = parseaddr(field)[1] if not to: continue mo = lre.match(to) if not mo: # This isn't an address we care about continue listname, subq = mo.group('listname', 'subq') if listname in listnames: break else: # As far as we can tell, this message isn't destined for # any list on the system. What to do? syslog('error', 'Message apparently not for any list: %s', xdstname) os.rename(dstname, xdstname) continue # BAW: blech, hardcoded msgdata = {'listname': listname} # -admin is deprecated if subq in ('bounces', 'admin'): queue = get_switchboard(mm_cfg.BOUNCEQUEUE_DIR) elif subq == 'confirm': msgdata['toconfirm'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq in ('join', 'subscribe'): msgdata['tojoin'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq in ('leave', 'unsubscribe'): msgdata['toleave'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq == 'owner': msgdata.update({ 'toowner': 1, 'envsender': Utils.get_site_email(extra='bounces'), 'pipeline': mm_cfg.OWNER_PIPELINE, }) queue = get_switchboard(mm_cfg.INQUEUE_DIR) elif subq is None: msgdata['tolist'] = 1 queue = get_switchboard(mm_cfg.INQUEUE_DIR) elif subq == 'request': msgdata['torequest'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) else: syslog('error', 'Unknown sub-queue: %s', subq) os.rename(dstname, xdstname) continue queue.enqueue(msg, msgdata) os.unlink(dstname) except Exception, e: os.rename(dstname, xdstname) syslog('error', str(e))
def listinfo_overview(msg=''): # Present the general listinfo overview hostname = Utils.get_domain() # Set up the document and assign it the correct language. The only one we # know about at the moment is the server's default. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) legend = _("%(hostname)s Mailing Lists") doc.SetTitle(legend) table = Table(border=0, width="100%") table.AddRow([Center(Header(2, legend))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, bgcolor=mm_cfg.WEB_HEADER_COLOR) # Skip any mailing lists that isn't advertised. advertised = [] listnames = Utils.list_names() listnames.sort() for name in listnames: mlist = MailList.MailList(name, lock=0) if mlist.advertised: if mm_cfg.VIRTUAL_HOST_OVERVIEW and ( mlist.web_page_url.find('/%s/' % hostname) == -1 and mlist.web_page_url.find('/%s:' % hostname) == -1): # List is for different identity of this host - skip it. continue else: advertised.append( (mlist.GetScriptURL('listinfo'), mlist.real_name, Utils.websafe(mlist.description))) if msg: greeting = FontAttr(msg, color="ff5060", size="+1") else: greeting = FontAttr(_('Welcome!'), size='+2') welcome = [greeting] mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() if not advertised: welcome.extend( _('''<p>There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.''')) else: welcome.append( _('''<p>Below is a listing of all the public mailing lists on %(hostname)s. Click on a list name to get more information about the list, or to subscribe, unsubscribe, and change the preferences on your subscription.''')) # set up some local variables adj = msg and _('right') or '' siteowner = Utils.get_site_email() welcome.extend( (_(''' To visit the general information page for an unadvertised list, open a URL similar to this one, but with a '/' and the %(adj)s list name appended. <p>List administrators, you can visit '''), Link(Utils.ScriptURL('admin'), _('the list admin overview page')), _(''' to find the management interface for your list. <p>If you are having trouble using the lists, please contact '''), Link('mailto:' + siteowner, siteowner), '.<p>')) table.AddRow([apply(Container, welcome)]) table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) if advertised: table.AddRow([' ', ' ']) table.AddRow([ Bold(FontAttr(_('List'), size='+2')), Bold(FontAttr(_('Description'), size='+2')) ]) highlight = 1 for url, real_name, description in advertised: table.AddRow([ Link(url, Bold(real_name)), description or Italic(_('[no description available]')) ]) if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR: table.AddRowInfo(table.GetCurrentRowIndex(), bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR) highlight = not highlight doc.AddItem(table) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format()
def _dispose(self, mlist, msg, msgdata): # Make sure we have the most up-to-date state mlist.Load() outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) # There are a few possibilities here: # # - the message could have been VERP'd in which case, we know exactly # who the message was destined for. That make our job easy. # - the message could have been originally destined for a list owner, # but a list owner address itself bounced. That's bad, and for now # we'll simply attempt to deliver the message to the site list # owner. # Note that this means that automated bounce processing doesn't work # for the site list. Because we can't reliably tell to what address # a non-VERP'd bounce was originally sent, we have to treat all # bounces sent to the site list as potential list owner bounces. # - the list owner could have set list-bounces (or list-admin) as the # owner address. That's really bad as it results in a loop of ever # growing unrecognized bounce messages. We detect this based on the # fact that this message itself will be from the site bounces # address. We then send this to the site list owner instead. # Notices to list-owner have their envelope sender and From: set to # the site-bounces address. Check if this is this a bounce for a # message to a list owner, coming to site-bounces, or a looping # message sent directly to the -bounces address. We have to do these # cases separately, because sending to site-owner will reset the # envelope sender. # Is this a site list bounce? if (mlist.internal_name().lower() == mm_cfg.MAILMAN_SITE_LIST.lower()): # Send it on to the site owners, but craft the envelope sender to # be the -loop detection address, so if /they/ bounce, we won't # get stuck in a bounce loop. outq.enqueue(msg, msgdata, recips=mlist.owner, envsender=Utils.get_site_email(extra='loop'), nodecorate=1, ) return # Is this a possible looping message sent directly to a list-bounces # address other than the site list? # Check From: because unix_from might be VERP'd. # Also, check the From: that Message.OwnerNotification uses. if (msg.get('from') == Utils.get_site_email(mlist.host_name, 'bounces')): # Just send it to the sitelist-owner address. If that bounces # we'll handle it above. outq.enqueue(msg, msgdata, recips=[Utils.get_site_email(extra='owner')], envsender=Utils.get_site_email(extra='loop'), nodecorate=1, ) return # List isn't doing bounce processing? if not mlist.bounce_processing: return # Try VERP detection first, since it's quick and easy addrs = verp_bounce(mlist, msg) if addrs: # We have an address, but check if the message is non-fatal. if BouncerAPI.ScanMessages(mlist, msg) is BouncerAPI.Stop: return else: # See if this was a probe message. token = verp_probe(mlist, msg) if token: self._probe_bounce(mlist, token) return # That didn't give us anything useful, so try the old fashion # bounce matching modules. addrs = BouncerAPI.ScanMessages(mlist, msg) if addrs is BouncerAPI.Stop: # This is a recognized, non-fatal notice. Ignore it. return # If that still didn't return us any useful addresses, then send it on # or discard it. addrs = filter(None, addrs) if not addrs: syslog('bounce', '%s: bounce message w/no discernable addresses: %s', mlist.internal_name(), msg.get('message-id', 'n/a')) maybe_forward(mlist, msg) return # BAW: It's possible that there are None's in the list of addresses, # although I'm unsure how that could happen. Possibly ScanMessages() # can let None's sneak through. In any event, this will kill them. # addrs = filter(None, addrs) # MAS above filter moved up so we don't try to queue an empty list. self._queue_bounces(mlist.internal_name(), addrs, msg)
def process(mlist, msg, msgdata): recips = msgdata.get('recips') if not recips: # Nobody to deliver to! return # Calculate the non-VERP envelope sender. envsender = msgdata.get('envsender') if envsender is None: if mlist: envsender = mlist.GetBouncesEmail() else: envsender = Utils.get_site_email(extra='bounces') # Time to split up the recipient list. If we're personalizing or VERPing # then each chunk will have exactly one recipient. We'll then hand craft # an envelope sender and stitch a message together in memory for each one # separately. If we're not VERPing, then we'll chunkify based on # SMTP_MAX_RCPTS. Note that most MTAs have a limit on the number of # recipients they'll swallow in a single transaction. deliveryfunc = None if ('personalize' not in msgdata or msgdata['personalize']) and ( msgdata.get('verp') or mlist.personalize): chunks = [[recip] for recip in recips] msgdata['personalize'] = 1 deliveryfunc = verpdeliver elif mm_cfg.SMTP_MAX_RCPTS <= 0: chunks = [recips] else: chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS) # See if this is an unshunted message for which some were undelivered if 'undelivered' in msgdata: chunks = msgdata['undelivered'] # If we're doing bulk delivery, then we can stitch up the message now. if deliveryfunc is None: # Be sure never to decorate the message more than once! if not msgdata.get('decorated'): Decorate.process(mlist, msg, msgdata) msgdata['decorated'] = True deliveryfunc = bulkdeliver refused = {} t0 = time.time() # Open the initial connection origrecips = msgdata['recips'] # MAS: get the message sender now for logging. If we're using 'sender' # and not 'from', bulkdeliver changes it for bounce processing. If we're # VERPing, it doesn't matter because bulkdeliver is working on a copy, but # otherwise msg gets changed. If the list is anonymous, the original # sender is long gone, but Cleanse.py has logged it. origsender = msgdata.get('original_sender', msg.get_sender()) # `undelivered' is a copy of chunks that we pop from to do deliveries. # This seems like a good tradeoff between robustness and resource # utilization. If delivery really fails (i.e. qfiles/shunt type # failures), then we'll pick up where we left off with `undelivered'. # This means at worst, the last chunk for which delivery was attempted # could get duplicates but not every one, and no recips should miss the # message. conn = Connection() try: msgdata['undelivered'] = chunks while chunks: chunk = chunks.pop() msgdata['recips'] = chunk try: deliveryfunc(mlist, msg, msgdata, envsender, refused, conn) except Exception: # If /anything/ goes wrong, push the last chunk back on the # undelivered list and re-raise the exception. We don't know # how many of the last chunk might receive the message, so at # worst, everyone in this chunk will get a duplicate. Sigh. chunks.append(chunk) raise del msgdata['undelivered'] finally: conn.quit() msgdata['recips'] = origrecips # Log the successful post t1 = time.time() d = MsgSafeDict(msg, {'time' : t1-t0, # BAW: Urg. This seems inefficient. 'size' : len(msg.as_string()), '#recips' : len(recips), '#refused': len(refused), 'listname': mlist.internal_name(), 'sender' : origsender, }) # We have to use the copy() method because extended call syntax requires a # concrete dictionary object; it does not allow a generic mapping. It's # still worthwhile doing the interpolation in syslog() because it'll catch # any catastrophic exceptions due to bogus format strings. if mm_cfg.SMTP_LOG_EVERY_MESSAGE: syslog.write_ex(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], mm_cfg.SMTP_LOG_EVERY_MESSAGE[1], kws=d) if refused: if mm_cfg.SMTP_LOG_REFUSED: syslog.write_ex(mm_cfg.SMTP_LOG_REFUSED[0], mm_cfg.SMTP_LOG_REFUSED[1], kws=d) elif msgdata.get('tolist'): # Log the successful post, but only if it really was a post to the # mailing list. Don't log sends to the -owner, or -admin addrs. # -request addrs should never get here. BAW: it may be useful to log # the other messages, but in that case, we should probably have a # separate configuration variable to control that. if mm_cfg.SMTP_LOG_SUCCESS: syslog.write_ex(mm_cfg.SMTP_LOG_SUCCESS[0], mm_cfg.SMTP_LOG_SUCCESS[1], kws=d) # Process any failed deliveries. tempfailures = [] permfailures = [] for recip, (code, smtpmsg) in list(refused.items()): # DRUMS is an internet draft, but it says: # # [RFC-821] incorrectly listed the error where an SMTP server # exhausts its implementation limit on the number of RCPT commands # ("too many recipients") as having reply code 552. The correct # reply code for this condition is 452. Clients SHOULD treat a 552 # code in this case as a temporary, rather than permanent failure # so the logic below works. # if code >= 500 and code != 552: # A permanent failure permfailures.append(recip) else: # Deal with persistent transient failures by queuing them up for # future delivery. TBD: this could generate lots of log entries! tempfailures.append(recip) if mm_cfg.SMTP_LOG_EACH_FAILURE: d.update({'recipient': recip, 'failcode' : code, 'failmsg' : smtpmsg}) syslog.write_ex(mm_cfg.SMTP_LOG_EACH_FAILURE[0], mm_cfg.SMTP_LOG_EACH_FAILURE[1], kws=d) # Return the results if tempfailures or permfailures: raise Errors.SomeRecipientsFailed(tempfailures, permfailures)
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 listinfo_overview(msg=''): # Present the general listinfo overview hostname = Utils.get_domain() # Set up the document and assign it the correct language. The only one we # know about at the moment is the server's default. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) legend = _("%(hostname)s Mailing Lists") doc.SetTitle(legend) table = Table(border=0, width="100%") table.AddRow([Center(Header(2, legend))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, bgcolor=mm_cfg.WEB_HEADER_COLOR) # Skip any mailing lists that isn't advertised. advertised = [] listnames = Utils.list_names() listnames.sort() for name in listnames: mlist = MailList.MailList(name, lock=0) if mlist.advertised: if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ mlist.web_page_url.find(hostname) == -1: # List is for different identity of this host - skip it. continue else: advertised.append((mlist.GetScriptURL('listinfo'), mlist.real_name, mlist.description)) if msg: greeting = FontAttr(msg, color="ff5060", size="+1") else: greeting = FontAttr(_('Welcome!'), size='+2') welcome = [greeting] mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() if not advertised: welcome.extend( _('''<p>There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.''')) else: welcome.append( _('''<p>Below is a listing of all the public mailing lists on %(hostname)s. Click on a list name to get more information about the list, or to subscribe, unsubscribe, and change the preferences on your subscription.''')) # set up some local variables adj = msg and _('right') or '' siteowner = Utils.get_site_email() welcome.extend( (_(''' To visit the general information page for an unadvertised list, open a URL similar to this one, but with a '/' and the %(adj)s list name appended. <p>List administrators, you can visit '''), Link(Utils.ScriptURL('admin'), _('the list admin overview page')), _(''' to find the management interface for your list. <p>If you are having trouble using the lists, please contact '''), Link('mailto:' + siteowner, siteowner), '.<p>')) welcome.extend( (_('''Users who are subscribed to one of the Mailing List can use OpenID to login with common credentials, to access all their subscribed List. <p> To register OpenID '''), Link(Utils.ScriptURL('openidreg'), _('Click Here')), _(''' to use OpenID for Systers'''), '.<p>')) table.AddRow([apply(Container, welcome)]) table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) if advertised: table.AddRow([' ', ' ']) table.AddRow([Bold(FontAttr(_('List'), size='+2')), Bold(FontAttr(_('Description'), size='+2')) ]) highlight = 1 for url, real_name, description in advertised: table.AddRow( [Link(url, Bold(real_name)), description or Italic(_('[no description available]'))]) if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR: table.AddRowInfo(table.GetCurrentRowIndex(), bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR) highlight = not highlight doc.AddItem(table) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format()
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 _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)
textSend = 1 tmp = '# FILE: %s #' % fileNameNew text.append('#' * len(tmp)) text.append(tmp) text.append('#' * len(tmp)) text.append('') linesLeft = showLines # e-mail first linesLeft of log files for line in fileinput.input(fileNameNew): if linesLeft == 0: text.append('[... truncated ...]') break linesLeft = linesLeft - 1 line = string.rstrip(line) text.append(line) text.append('') # send message if we've actually found anything if textSend: text = string.join(text, '\n') + '\n' siteowner = Utils.get_site_email() Utils.SendTextToUser( 'Mailman Log Report -- %s' % time.ctime(time.time()), text, siteowner, siteowner) # compress any log-files we made if hasattr(mm_cfg, 'COMPRESS_LOGFILES_WITH') and mm_cfg.COMPRESS_LOGFILES_WITH: for file in newLogfiles: os.system(mm_cfg.COMPRESS_LOGFILES_WITH % file)
problems += 1 syslog('error', 'directory %s not deleted due to permission problems', dir) title = _('Mailing list deletion results') doc.SetTitle(title) table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) if not problems: table.AddRow([_('''You have successfully deleted the mailing list <b>%(listname)s</b>.''')]) else: sitelist = Utils.get_site_email(mlist.host_name) table.AddRow([_('''There were some problems deleting the mailing list <b>%(listname)s</b>. Contact your site administrator at %(sitelist)s for details.''')]) doc.AddItem(table) doc.AddItem('<hr>') doc.AddItem(_('Return to the ') + Link(Utils.ScriptURL('listinfo'), _('general list overview')).Format()) doc.AddItem(_('<br>Return to the ') + Link(Utils.ScriptURL('admin'), _('administrative list overview')).Format()) doc.AddItem(MailmanLogo())
pass # permission denied, DOH! textSend = 1 tmp = '# FILE: %s #' % fileNameNew text.append('#' * len(tmp)) text.append(tmp) text.append('#' * len(tmp)) text.append('') linesLeft = showLines # e-mail first linesLeft of log files for line in fileinput.input(fileNameNew): if linesLeft == 0: text.append('[... truncated ...]') break linesLeft = linesLeft - 1 line = string.rstrip(line) text.append(line) text.append('') # send message if we've actually found anything if textSend: text = string.join(text, '\n') + '\n' siteowner = Utils.get_site_email() Utils.SendTextToUser('Mailman Log Report -- %s' % time.ctime(time.time()), text, siteowner, siteowner) # compress any log-files we made if hasattr(mm_cfg, 'COMPRESS_LOGFILES_WITH') and mm_cfg.COMPRESS_LOGFILES_WITH: for file in newLogfiles: os.system(mm_cfg.COMPRESS_LOGFILES_WITH % file)
def _oneloop(self): # Refresh this each time through the list. BAW: could be too # expensive. listnames = Utils.list_names() # Cruise through all the files currently in the new/ directory try: files = os.listdir(self._dir) except OSError as e: if e.errno != errno.ENOENT: raise # Nothing's been delivered yet return 0 for file in files: srcname = os.path.join(self._dir, file) dstname = os.path.join(self._cur, file + ':1,P') xdstname = os.path.join(self._cur, file + ':1,X') try: os.rename(srcname, dstname) except OSError as e: if e.errno == errno.ENOENT: # Some other MaildirRunner beat us to it continue syslog('error', 'Could not rename maildir file: %s', srcname) raise # Now open, read, parse, and enqueue this message try: fp = open(dstname) try: msg = self._parser.parse(fp) finally: fp.close() # Now we need to figure out which queue of which list this # message was destined for. See verp_bounce() in # BounceRunner.py for why we do things this way. vals = [] for header in ('delivered-to', 'envelope-to', 'apparently-to'): vals.extend(msg.get_all(header, [])) for field in vals: to = parseaddr(field)[1] if not to: continue mo = lre.match(to) if not mo: # This isn't an address we care about continue listname, subq = mo.group('listname', 'subq') if listname in listnames: break else: # As far as we can tell, this message isn't destined for # any list on the system. What to do? syslog('error', 'Message apparently not for any list: %s', xdstname) os.rename(dstname, xdstname) continue # BAW: blech, hardcoded msgdata = {'listname': listname} # -admin is deprecated if subq in ('bounces', 'admin'): queue = get_switchboard(mm_cfg.BOUNCEQUEUE_DIR) elif subq == 'confirm': msgdata['toconfirm'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq in ('join', 'subscribe'): msgdata['tojoin'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq in ('leave', 'unsubscribe'): msgdata['toleave'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq == 'owner': msgdata.update({ 'toowner': 1, 'envsender': Utils.get_site_email(extra='bounces'), 'pipeline': mm_cfg.OWNER_PIPELINE, }) queue = get_switchboard(mm_cfg.INQUEUE_DIR) elif subq is None: msgdata['tolist'] = 1 queue = get_switchboard(mm_cfg.INQUEUE_DIR) elif subq == 'request': msgdata['torequest'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) else: syslog('error', 'Unknown sub-queue: %s', subq) os.rename(dstname, xdstname) continue queue.enqueue(msg, msgdata) os.unlink(dstname) except Exception as e: os.rename(dstname, xdstname) syslog('error', str(e))
def process(mlist, msg, msgdata): recips = msgdata.get('recips') if not recips: # Nobody to deliver to! return # Calculate the non-VERP envelope sender. envsender = msgdata.get('envsender') if envsender is None: if mlist: envsender = mlist.GetBouncesEmail() else: envsender = Utils.get_site_email(extra='bounces') # Time to split up the recipient list. If we're personalizing or VERPing # then each chunk will have exactly one recipient. We'll then hand craft # an envelope sender and stitch a message together in memory for each one # separately. If we're not VERPing, then we'll chunkify based on # SMTP_MAX_RCPTS. Note that most MTAs have a limit on the number of # recipients they'll swallow in a single transaction. deliveryfunc = None if (not msgdata.has_key('personalize') or msgdata['personalize']) and ( msgdata.get('verp') or mlist.personalize): chunks = [[recip] for recip in recips] msgdata['personalize'] = 1 deliveryfunc = verpdeliver elif mm_cfg.SMTP_MAX_RCPTS <= 0: chunks = [recips] else: chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS) # See if this is an unshunted message for which some were undelivered if msgdata.has_key('undelivered'): chunks = msgdata['undelivered'] # If we're doing bulk delivery, then we can stitch up the message now. if deliveryfunc is None: # Be sure never to decorate the message more than once! if not msgdata.get('decorated'): Decorate.process(mlist, msg, msgdata) msgdata['decorated'] = True deliveryfunc = bulkdeliver refused = {} t0 = time.time() # Open the initial connection origrecips = msgdata['recips'] # MAS: get the message sender now for logging. If we're using 'sender' # and not 'from', bulkdeliver changes it for bounce processing. If we're # VERPing, it doesn't matter because bulkdeliver is working on a copy, but # otherwise msg gets changed. If the list is anonymous, the original # sender is long gone, but Cleanse.py has logged it. origsender = msgdata.get('original_sender', msg.get_sender()) # `undelivered' is a copy of chunks that we pop from to do deliveries. # This seems like a good tradeoff between robustness and resource # utilization. If delivery really fails (i.e. qfiles/shunt type # failures), then we'll pick up where we left off with `undelivered'. # This means at worst, the last chunk for which delivery was attempted # could get duplicates but not every one, and no recips should miss the # message. conn = Connection() try: msgdata['undelivered'] = chunks while chunks: chunk = chunks.pop() msgdata['recips'] = chunk try: deliveryfunc(mlist, msg, msgdata, envsender, refused, conn) except Exception: # If /anything/ goes wrong, push the last chunk back on the # undelivered list and re-raise the exception. We don't know # how many of the last chunk might receive the message, so at # worst, everyone in this chunk will get a duplicate. Sigh. chunks.append(chunk) raise del msgdata['undelivered'] finally: conn.quit() msgdata['recips'] = origrecips # Log the successful post t1 = time.time() d = MsgSafeDict(msg, {'time' : t1-t0, # BAW: Urg. This seems inefficient. 'size' : len(msg.as_string()), '#recips' : len(recips), '#refused': len(refused), 'listname': mlist.internal_name(), 'sender' : origsender, }) # We have to use the copy() method because extended call syntax requires a # concrete dictionary object; it does not allow a generic mapping. It's # still worthwhile doing the interpolation in syslog() because it'll catch # any catastrophic exceptions due to bogus format strings. if mm_cfg.SMTP_LOG_EVERY_MESSAGE: syslog.write_ex(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], mm_cfg.SMTP_LOG_EVERY_MESSAGE[1], kws=d) if refused: if mm_cfg.SMTP_LOG_REFUSED: syslog.write_ex(mm_cfg.SMTP_LOG_REFUSED[0], mm_cfg.SMTP_LOG_REFUSED[1], kws=d) elif msgdata.get('tolist'): # Log the successful post, but only if it really was a post to the # mailing list. Don't log sends to the -owner, or -admin addrs. # -request addrs should never get here. BAW: it may be useful to log # the other messages, but in that case, we should probably have a # separate configuration variable to control that. if mm_cfg.SMTP_LOG_SUCCESS: syslog.write_ex(mm_cfg.SMTP_LOG_SUCCESS[0], mm_cfg.SMTP_LOG_SUCCESS[1], kws=d) # Process any failed deliveries. tempfailures = [] permfailures = [] for recip, (code, smtpmsg) in refused.items(): # DRUMS is an internet draft, but it says: # # [RFC-821] incorrectly listed the error where an SMTP server # exhausts its implementation limit on the number of RCPT commands # ("too many recipients") as having reply code 552. The correct # reply code for this condition is 452. Clients SHOULD treat a 552 # code in this case as a temporary, rather than permanent failure # so the logic below works. # if code >= 500 and code <> 552: # A permanent failure permfailures.append(recip) else: # Deal with persistent transient failures by queuing them up for # future delivery. TBD: this could generate lots of log entries! tempfailures.append(recip) if mm_cfg.SMTP_LOG_EACH_FAILURE: d.update({'recipient': recip, 'failcode' : code, 'failmsg' : smtpmsg}) syslog.write_ex(mm_cfg.SMTP_LOG_EACH_FAILURE[0], mm_cfg.SMTP_LOG_EACH_FAILURE[1], kws=d) # Return the results if tempfailures or permfailures: raise Errors.SomeRecipientsFailed(tempfailures, permfailures)
def _dispose(self, mlist, msg, msgdata): # Make sure we have the most up-to-date state mlist.Load() outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) # There are a few possibilities here: # # - the message could have been VERP'd in which case, we know exactly # who the message was destined for. That make our job easy. # - the message could have been originally destined for a list owner, # but a list owner address itself bounced. That's bad, and for now # we'll simply attempt to deliver the message to the site list # owner. # Note that this means that automated bounce processing doesn't work # for the site list. Because we can't reliably tell to what address # a non-VERP'd bounce was originally sent, we have to treat all # bounces sent to the site list as potential list owner bounces. # - the list owner could have set list-bounces (or list-admin) as the # owner address. That's really bad as it results in a loop of ever # growing unrecognized bounce messages. We detect this based on the # fact that this message itself will be from the site bounces # address. We then send this to the site list owner instead. # Notices to list-owner have their envelope sender and From: set to # the site-bounces address. Check if this is this a bounce for a # message to a list owner, coming to site-bounces, or a looping # message sent directly to the -bounces address. We have to do these # cases separately, because sending to site-owner will reset the # envelope sender. # Is this a site list bounce? if (mlist.internal_name().lower() == mm_cfg.MAILMAN_SITE_LIST.lower()): # Send it on to the site owners, but craft the envelope sender to # be the -loop detection address, so if /they/ bounce, we won't # get stuck in a bounce loop. outq.enqueue( msg, msgdata, recips=mlist.owner, envsender=Utils.get_site_email(extra='loop'), nodecorate=1, ) return # Is this a possible looping message sent directly to a list-bounces # address other than the site list? # Check From: because unix_from might be VERP'd. # Also, check the From: that Message.OwnerNotification uses. if (msg.get('from') == Utils.get_site_email(mlist.host_name, 'bounces')): # Just send it to the sitelist-owner address. If that bounces # we'll handle it above. outq.enqueue( msg, msgdata, recips=[Utils.get_site_email(extra='owner')], envsender=Utils.get_site_email(extra='loop'), nodecorate=1, ) return # List isn't doing bounce processing? if not mlist.bounce_processing: return # Try VERP detection first, since it's quick and easy addrs = verp_bounce(mlist, msg) if addrs: # We have an address, but check if the message is non-fatal. if BouncerAPI.ScanMessages(mlist, msg) is BouncerAPI.Stop: return else: # See if this was a probe message. token = verp_probe(mlist, msg) if token: self._probe_bounce(mlist, token) return # That didn't give us anything useful, so try the old fashion # bounce matching modules. addrs = BouncerAPI.ScanMessages(mlist, msg) if addrs is BouncerAPI.Stop: # This is a recognized, non-fatal notice. Ignore it. return # If that still didn't return us any useful addresses, then send it on # or discard it. addrs = filter(None, addrs) if not addrs: syslog('bounce', '%s: bounce message w/no discernable addresses: %s', mlist.internal_name(), msg.get('message-id', 'n/a')) maybe_forward(mlist, msg) return # BAW: It's possible that there are None's in the list of addresses, # although I'm unsure how that could happen. Possibly ScanMessages() # can let None's sneak through. In any event, this will kill them. # addrs = filter(None, addrs) # MAS above filter moved up so we don't try to queue an empty list. self._queue_bounces(mlist.internal_name(), addrs, msg)
def process_request(doc, cgidata, mlist): password = cgidata.getfirst('password', '').strip() try: delarchives = int(cgidata.getfirst('delarchives', '0')) except ValueError: delarchives = 0 # Removing a list is limited to the list-creator (a.k.a. list-destroyer), # the list-admin, or the site-admin. Don't use WebAuthenticate here # because we want to be sure the actual typed password is valid, not some # password sitting in a cookie. if mlist.Authenticate( (mm_cfg.AuthCreator, mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), password) == mm_cfg.UnAuthorized: request_deletion( doc, mlist, _('You are not authorized to delete this mailing list')) return # Do the MTA-specific list deletion tasks if mm_cfg.MTA: modname = 'Mailman.MTA.' + mm_cfg.MTA __import__(modname) sys.modules[modname].remove(mlist, cgi=1) REMOVABLES = ['lists/%s'] if delarchives: REMOVABLES.extend([ 'archives/private/%s', 'archives/private/%s.mbox', 'archives/public/%s', 'archives/public/%s.mbox', ]) problems = 0 listname = mlist.internal_name() for dirtmpl in REMOVABLES: dir = os.path.join(mm_cfg.VAR_PREFIX, dirtmpl % listname) if os.path.islink(dir): try: os.unlink(dir) except OSError as e: if e.errno not in (errno.EACCES, errno.EPERM): raise problems += 1 syslog('error', 'link %s not deleted due to permission problems', dir) elif os.path.isdir(dir): try: shutil.rmtree(dir) except OSError as e: if e.errno not in (errno.EACCES, errno.EPERM): raise problems += 1 syslog('error', 'directory %s not deleted due to permission problems', dir) title = _('Mailing list deletion results') doc.SetTitle(title) table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) if not problems: table.AddRow([ _('''You have successfully deleted the mailing list <b>%(listname)s</b>.''') ]) else: sitelist = Utils.get_site_email(mlist.host_name) table.AddRow([ _('''There were some problems deleting the mailing list <b>%(listname)s</b>. Contact your site administrator at %(sitelist)s for details.''') ]) doc.AddItem(table) doc.AddItem('<hr>') doc.AddItem( _('Return to the ') + Link(Utils.ScriptURL('listinfo'), _('general list overview')).Format()) doc.AddItem( _('<br>Return to the ') + Link(Utils.ScriptURL( 'admin'), _('administrative list overview')).Format()) doc.AddItem(MailmanLogo())