def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) cgidata = cgi.FieldStorage() parts = Utils.GetPathPieces() if parts: # Bad URL specification title = _('Bad URL specification') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) syslog('error', 'Bad URL specification: %s', parts) elif cgidata.has_key('doit'): # We must be processing the list creation request process_request(doc, cgidata) elif cgidata.has_key('clear'): request_creation(doc) else: # Put up the list creation request form request_creation(doc) doc.AddItem('<hr>') # Always add the footer and print the document 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()) print doc.Format()
def add_members (self, save=False): """Add any email addressses that are provided in the create form.""" if self.welcome == '': text = ('Welcome to %s. Visit the List Server to ' + 'manage your subscriptions') % self.ln else: text = self.welcome for key in self.cgidata.keys(): if re.match('^lc_member_', key): fn, em = parseaddr(self.cgival(key).lower().strip()) userdesc = UserDesc(em, fn, mm_cfg.SSO_STOCK_USER_PWD, False) try: self.ml.ApprovedAddMember(userdesc, True, text, whence='SSO List Creation Time') syslog('sso', 'Successfully added %s to list: %s' % (em, self.ln)) except Errors.MMAlreadyAMember: ## FIXME: Need to find some way of communicating this ## case to the user. As thisi s a new list, this can only ## happen if the same address is given by the admin... hm syslog('sso', '%s already a member of listL %s' % (em, self.ln)) if save: self.ml.Save()
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 report_submission(msgid, message, inprogress=False): """ :return: URL of the HTML document """ if not mm_cfg.POST_TRACKING_URLBASE or not mm_cfg.POST_TRACKING_PATH: return "" sha1hex = sha_new(msgid).hexdigest() fname = "%s.html" % sha1hex tmpname = ".%s.tmp" % sha1hex fullfname = os.path.join(mm_cfg.POST_TRACKING_PATH, fname) tmpname = os.path.join(mm_cfg.POST_TRACKING_PATH, tmpname) doc = """<html><head><title>Mailman tracker</title>%s</head><body> <h3>Message ID %s</h3> <p> %s </p> </body></html> """ meta = '<meta http-equiv="refresh" content="30"/>' if inprogress else "" try: with open(tmpname, "w") as reportfile: reportfile.write(doc % (meta, websafe(msgid), message)) os.rename(tmpname, fullfname) except OSError, e: syslog("error", "report_submission failed: %s", e) return ""
def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # Do some final cleanup of the message header. Start by blowing away # any the Sender: and Errors-To: headers so remote MTAs won't be # tempted to delivery bounces there instead of our envelope sender # # BAW An interpretation of RFCs 2822 and 2076 could argue for not touching # the Sender header at all. Brad Knowles points out that MTAs tend to # wipe existing Return-Path headers, and old MTAs may still honor # Errors-To while new ones will at worst ignore the header. del msg['sender'] del msg['errors-to'] msg['Sender'] = envsender msg['Errors-To'] = envsender # Get the plain, flattened text of the message, sans unixfrom msgtext = msg.as_string() refused = {} recips = msgdata['recips'] msgid = msg['message-id'] try: # Send the message refused = conn.sendmail(envsender, recips, msgtext) except smtplib.SMTPRecipientsRefused, e: syslog('smtp-failure', 'All recipients refused: %s, msgid: %s', e, msgid) refused = e.recipients
def maybe_forward(mlist, msg): # Does the list owner want to get non-matching bounce messages? # If not, simply discard it. if mlist.bounce_unrecognized_goes_to_list_owner: adminurl = mlist.GetScriptURL('admin', absolute=1) + '/bounce' mlist.ForwardMessage(msg, text=_("""\ The attached message was received as a bounce, but either the bounce format was not recognized, or no member addresses could be extracted from it. This mailing list has been configured to send all unrecognized bounce messages to the list administrator(s). For more information see: %(adminurl)s """), subject=_('Uncaught bounce notification'), tomoderators=0) syslog('bounce', '%s: forwarding unrecognized, message-id: %s', mlist.internal_name(), msg.get('message-id', 'n/a')) else: syslog('bounce', '%s: discarding unrecognized, message-id: %s', mlist.internal_name(), msg.get('message-id', 'n/a'))
def do_discard(mlist, msg): sender = msg.get_sender() # Do we forward auto-discards to the list owners? if mlist.forward_auto_discards: lang = mlist.preferred_language varhelp = '%s/?VARHELP=privacy/sender/discard_these_nonmembers' % \ mlist.GetScriptURL('admin', absolute=1) nmsg = Message.UserNotification(mlist.GetOwnerEmail(), mlist.GetBouncesEmail(), _('Auto-discard notification'), lang=lang) nmsg.set_type('multipart/mixed') text = MIMEText(Utils.wrap(_( 'The attached message has been automatically discarded.')), _charset=Utils.GetCharSet(lang)) nmsg.attach(text) decrypted = msg.get('X-Mailman-SLS-decrypted', '').lower() if decrypted == 'yes': syslog('gpg', 'forwarding only headers of message from %s to listmaster to notify discard since message was decrypted', sender) msgtext = msg.as_string() (header, body) = msgtext.split("\n\n", 1) nmsg.attach(MIMEText(header)) else: nmsg.attach(MIMEMessage(msg)) nmsg.send(mlist) # Discard this sucker raise Errors.DiscardMessage
def importAllSubscriberKeys(self): gpg = self.getGPGObject() p = gpg.run(['--import'],create_fhs=['stdin','stdout','stderr']) t_out = AsyncRead(p.handles['stdout']) t_out.start() t_err = AsyncRead(p.handles['stderr']) t_err.start() for user in self.mlist.getMembers(): key = self.mlist.getGPGKey(user) if key: p.handles['stdin'].write(key) p.handles['stdin'].close() t_out.join() t_err.join() # Ignore date from t_out result = t_err.data try: p.wait() except IOError: syslog('gpg','Error importing keys: %s' % result) return None self.checkPerms() key_ids= [] for line in result.lower().splitlines(): g = re.search('key ([0-9a-f]+):',line) if g!=None: key_ids.append('0x%s' % g.groups()[0]) return key_ids
def process(mlist, msg, msgdata): # Short circuit non-digestable lists. if not mlist.digestable or msgdata.get('isdigest'): return mboxfile = os.path.join(mlist.fullpath(), 'digest.mbox') omask = os.umask(007) try: mboxfp = open(mboxfile, 'a+') finally: os.umask(omask) mbox = Mailbox(mboxfp) mbox.AppendMessage(msg) # Calculate the current size of the accumulation file. This will not tell # us exactly how big the MIME, rfc1153, or any other generated digest # message will be, but it's the most easily available metric to decide # whether the size threshold has been reached. mboxfp.flush() size = os.path.getsize(mboxfile) if size / 1024.0 >= mlist.digest_size_threshhold: # This is a bit of a kludge to get the mbox file moved to the digest # queue directory. try: # Enclose in try/except here because a error in send_digest() can # silently stop regular delivery. Unsuccessful digest delivery # should be tried again by cron and the site administrator will be # notified of any error explicitly by the cron error message. mboxfp.seek(0) send_digests(mlist, mboxfp) os.unlink(mboxfile) except Exception, errmsg: # Bare except is generally prohibited in Mailman, but we can't # forecast what exceptions can occur here. syslog('error', 'send_digests() failed: %s', errmsg)
def request_edit (self): self._ml = self.all_mls[self.ln] err = self.errcheck(action='edit') if err: return err # We've got all the data we need, so go ahead and try to edit the # list See admin.py for why we need to set up the signal handler. try: signal.signal(signal.SIGTERM, self.sigterm_handler) self.ml.Lock() self.set_ml_params() self.edit_members() self.set_ml_owners() self.ml.Save() syslog('sso', 'Successfully modified list config: %s' % self.ln) finally: # Now be sure to unlock the list. It's okay if we get a signal # here because essentially, the signal handler will do the same # thing. And unlocking is unconditional, so it's not an error if # we unlock while we're already unlocked. self.ml.Unlock() return None
def decorate(mlist, template, what, extradict=None): # `what' is just a descriptive phrase used in the log message # # BAW: We've found too many situations where Python can be fooled into # interpolating too much revealing data into a format string. For # example, a footer of "% silly %(real_name)s" would give a header # containing all list attributes. While we've previously removed such # really bad ones like `password' and `passwords', it's much better to # provide a whitelist of known good attributes, then to try to remove a # blacklist of known bad ones. d = SafeDict({'real_name' : mlist.real_name, 'list_name' : mlist.internal_name(), # For backwards compatibility '_internal_name': mlist.internal_name(), 'host_name' : mlist.host_name, 'web_page_url' : mlist.web_page_url, 'description' : mlist.description, 'info' : mlist.info, 'cgiext' : mm_cfg.CGIEXT, }) if extradict is not None: d.update(extradict) # Using $-strings? if getattr(mlist, 'use_dollar_strings', 0): template = Utils.to_percent(template) # Interpolate into the template try: text = re.sub(r'(?m)(?<!^--) +(?=\n)', '', re.sub(r'\r\n', r'\n', template % d)) except (ValueError, TypeError), e: syslog('error', 'Exception while calculating %s:\n%s', what, e) text = template
def __init__(self, mlist, param): self._param = param self.__mlist = mlist self._members = None self._member_passwd = {} self._member_names = {} self._updatetime = 0 # define the table and standard condition reflecting listname self._table = param["mailman_table"] self._where = "listname = '%s'" % (self.__mlist.internal_name()) # define query for session management self._cookiename = param["cookiename"] self._queryCookieMail = param["queryCookieMail"] self._queryCookieId = param["queryCookieId"] self._queryIsAdmin = param["queryIsAdmin"] self._queryIsSiteAdmin = param["queryIsSiteAdmin"] self._queryIsMonitoring = param["queryIsMonitoring"] self.__db_connect__() if mm_cfg.MYSQL_MEMBER_DB_VERBOSE: # Message to indicate successful init. message = "DBMemberships " + "$Revision: 1.69 $ initialized with host: %s (%s)" % ( mm_cfg.connection.get_host_info(), mm_cfg.connection.get_server_info(), ) syslog("error", message) syslog("mysql", message) # add a cache memory self._cache = {} self._cachedate = 0
def _heartbeat(self): """Add a heartbeat to the log for a monitor to watch.""" now = datetime.now() last_heartbeat = self.last_heartbeat if last_heartbeat is None or now - last_heartbeat >= self.heartbeat_frequency: syslog("xmlrpc", "--MARK--") self.last_heartbeat = now
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) cgidata = cgi.FieldStorage() parts = Utils.GetPathPieces() if not parts: # Bad URL specification title = _('Bad URL specification') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() syslog('error', 'Bad URL specification: %s', parts) return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() syslog('error', 'No such list "%s": %s\n', listname, e) return
def Secure_MakeRandomPassword(length): bytesread = 0 bytes = [] fd = None try: while bytesread < length: try: # Python 2.4 has this on available systems. newbytes = os.urandom(length - bytesread) except (AttributeError, NotImplementedError): if fd is None: try: fd = os.open('/dev/urandom', os.O_RDONLY) except OSError, e: if e.errno <> errno.ENOENT: raise # We have no available source of cryptographically # secure random characters. Log an error and fallback # to the user friendly passwords. syslog('error', 'urandom not available, passwords not secure') return UserFriendly_MakeRandomPassword(length) newbytes = os.read(fd, length - bytesread) bytes.append(newbytes) bytesread += len(newbytes) s = base64.encodestring(EMPTYSTRING.join(bytes)) # base64 will expand the string by 4/3rds return s.replace('\n', '')[:length]
def HoldUnsubscription(self, addr): # Assure the database is open for writing self.__opendb() # Get the next unique id id = self.__nextid() # All we need to do is save the unsubscribing address self.__db[id] = (UNSUBSCRIPTION, addr) syslog('vette', '%s: held unsubscription request from %s', self.internal_name(), addr) # Possibly notify the administrator of the hold if self.admin_immed_notify: realname = self.real_name subject = _( 'New unsubscription request from %(realname)s by %(addr)s') text = Utils.maketext( 'unsubauth.txt', {'username' : addr, ## cpanel patch 'listname' : self.real_name, 'hostname' : self.host_name, 'admindb_url': self.GetScriptURL('admindb', absolute=1), }, mlist=self) # This message should appear to come from the <list>-owner so as # to avoid any useless bounce processing. owneraddr = self.GetOwnerEmail() msg = Message.UserNotification(owneraddr, owneraddr, subject, text, self.preferred_language) msg.send(self, **{'tomoderators': 1})
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() lenparts = parts and len(parts) if not parts or lenparts < 1: title = _("CGI script error") doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_("Invalid options to CGI script.")) doc.AddItem("<hr>") doc.AddItem(MailmanLogo()) print doc.Format() return # get the list and user's name listname = parts[0].lower() # open list try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _("CGI script error") doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_("No such list <em>%(safelistname)s</em>")) doc.AddItem("<hr>") doc.AddItem(MailmanLogo()) # Send this with a 404 status. print "Status: 404 Not Found" print doc.Format() syslog("error", 'No such list "%s": %s\n', listname, e) return
def encryptSignMessage(self,msg,recipients): gpg = self.getGPGObject() params = ['--encrypt','--sign','--always-trust','--batch','--no-permission-warning'] for i in recipients: params.append('-r') params.append(i) p = gpg.run(params, create_fhs=['stdin','stdout','stderr','passphrase']) t_out = AsyncRead(p.handles['stdout']) t_out.start() t_err = AsyncRead(p.handles['stderr']) t_err.start() p.handles['passphrase'].write(self.mlist.gpg_passphrase) p.handles['passphrase'].close() p.handles['stdin'].write(msg) p.handles['stdin'].close() t_out.join() t_err.join() ciphertext = t_out.data result = t_err.data try: p.wait() except IOError: syslog('gpg',"Error encrypting message: %s",result) return None return ciphertext
def db_export(mlist): try: rootdir = mlist.archive_dir() conn = db_conn(mlist) return _db_export(conn,rootdir) except Exception,e: syslog('gpg','%s' % e)
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 _dopipeline(self, mlist, msg, msgdata, pipeline): while pipeline: handler = pipeline.pop(0) modname = 'Mailman.Handlers.' + handler __import__(modname) try: pid = os.getpid() sys.modules[modname].process(mlist, msg, msgdata) # Failsafe -- a child may have leaked through. if pid <> os.getpid(): syslog('error', 'child process leaked thru: %s', modname) os._exit(1) except Errors.DiscardMessage: # Throw the message away; we need do nothing else with it. syslog('vette', 'Message discarded, msgid: %s', msg.get('message-id', 'n/a')) return 0 except Errors.HoldMessage: # Let the approval process take it from here. The message no # longer needs to be queued. return 0 except Errors.RejectMessage, e: mlist.BounceMessage(msg, msgdata, e) return 0 except:
def _dispose(self, mlist, msg, msgdata): # Make sure we have the most up-to-date state mlist.Load() if not msgdata.get('prepped'): prepare_message(mlist, msg, msgdata) try: # Flatten the message object, sticking it in a StringIO object fp = StringIO(msg.as_string()) conn = None try: try: nntp_host, nntp_port = Utils.nntpsplit(mlist.nntp_host) conn = nntplib.NNTP(nntp_host, nntp_port, readermode=True, user=mm_cfg.NNTP_USERNAME, password=mm_cfg.NNTP_PASSWORD) conn.post(fp) except nntplib.error_temp, e: syslog('error', '(NNTPDirect) NNTP error for list "%s": %s', mlist.internal_name(), e) except socket.error, e: syslog('error', '(NNTPDirect) socket error for list "%s": %s', mlist.internal_name(), e) finally: if conn: conn.quit()
def __handlesubscription(self, record, value, comment): stime, addr, fullname, password, digest, lang = record if value == mm_cfg.DEFER: return DEFER elif value == mm_cfg.DISCARD: syslog('vette', '%s: discarded subscription request from %s', self.internal_name(), addr) elif value == mm_cfg.REJECT: self.__refuse(_('Subscription request'), addr, comment or _('[No reason given]'), lang=lang) syslog('vette', """%s: rejected subscription request from %s tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') else: # subscribe assert value == mm_cfg.SUBSCRIBE try: userdesc = UserDesc(addr, fullname, password, digest, lang) self.ApprovedAddMember(userdesc, whence='via admin approval') except Errors.MMAlreadyAMember: # User has already been subscribed, after sending the request pass # TBD: disgusting hack: ApprovedAddMember() can end up closing # the request database. self.__opendb() return REMOVE
def process(mlist, msg, msgdata): """Process the message object for the given list. The message object is an instance of Mailman.Message and must be fully prepared for delivery (i.e. all the appropriate headers must be set). The message object can have the following attributes: recips - the list of recipients for the message (required) This function processes the message by handing off the delivery of the message to a sendmail (or sendmail clone) program. It can raise a SendmailHandlerError if an error status was returned by the sendmail program. """ recips = msgdata.get('recips') if not recips: # Nobody to deliver to! return # Use -f to set the envelope sender cmd = mm_cfg.SENDMAIL_CMD + ' -f ' + mlist.GetAdminEmail() + ' ' # make sure the command line is of a manageable size recipchunks = [] currentchunk = [] chunklen = 0 for r in recips: currentchunk.append(r) chunklen = chunklen + len(r) + 1 if chunklen > MAX_CMDLINE: recipchunks.append(string.join(currentchunk)) currentchunk = [] chunklen = 0 # pick up the last one if chunklen: recipchunks.append(string.join(currentchunk)) # get all the lines of the message, since we're going to do this over and # over again msgtext = str(msg) # cycle through all chunks failedrecips = [] for chunk in recipchunks: # TBD: SECURITY ALERT. This invokes the shell! fp = os.popen(cmd + chunk, 'w') fp.write(msgtext) status = fp.close() if status: errcode = (status & 0xff00) >> 8 syslog('post', 'post to %s from %s, size=%d, failure=%d' % (mlist.internal_name(), msg.GetSender(), len(msg.body), errcode)) # TBD: can we do better than this? What if only one recipient out # of the entire chunk failed? failedrecips.append(chunk) # Log the successful post syslog('post', 'post to %s from %s, size=%d, success' % (mlist.internal_name(), msg.GetSender(), len(msg.body))) if failedrecips: msgdata['recips'] = failedrecips raise HandlerAPI.SomeRecipientsFailed
def AddOptionsTableItem(table, item, category, mlist, detailsp=1): """Add a row to an options table with the item description and value.""" try: got = GetItemCharacteristics(item) varname, kind, params, dependancies, descr, elaboration = got except ValueError, msg: syslog('error', 'admin: %s' % msg) return Italic("<malformed option>")
def GetPathPieces(envar='PATH_INFO'): path = os.environ.get(envar) if path: if CRNLpat.search(path): path = CRNLpat.split(path)[0] syslog('error', 'Warning: Possible malformed path attack.') return [p for p in path.split('/') if p] return None
def GetPathPieces(envar="PATH_INFO"): path = os.environ.get(envar) if path: if CRNLpat.search(path): path = CRNLpat.split(path)[0] syslog("error", "Warning: Possible malformed path attack.") return [p for p in path.split("/") if p] return None
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.SetTitle(_("Private Archive Error")) doc.AddItem(Header(3, _("You must specify a list."))) print doc.Format() return path = os.environ.get('PATH_INFO') tpath = true_path(path) if tpath <> path[1:]: msg = _('Private archive - "./" and "../" not allowed in URL.') doc.SetTitle(msg) doc.AddItem(Header(2, msg)) print doc.Format() syslog('mischief', 'Private archive hostile path: %s', path) return # BAW: This needs to be converted to the Site module abstraction true_filename = os.path.join( mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) listname = parts[0].lower() mboxfile = '' if len(parts) > 1: mboxfile = parts[1] # See if it's the list's mbox file is being requested if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \ listname[:-5] == mboxfile[:-5]: listname = listname[:-5] else: mboxfile = '' # If it's a directory, we have to append index.html in this script. We # must also check for a gzipped file, because the text archives are # usually stored in compressed form. if os.path.isdir(true_filename): true_filename = true_filename + '/index.html' if not os.path.exists(true_filename) and \ os.path.exists(true_filename + '.gz'): true_filename = true_filename + '.gz' try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) msg = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(_("Private Archive Error - %(msg)s")) doc.AddItem(Header(2, msg)) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'private: No such list "%s": %s\n', listname, e) return
def getSMIMEMemberCertFile(self, member): recipfile = self._getSMIMEMemberCertFile(member) if not os.access(recipfile,os.F_OK): syslog('gpg', "No Member SMIME Certfile '%s' found", recipfile) return None syslog('gpg', "Using Member SMIME Certfile '%s'", recipfile) return recipfile
def process_request(doc, cgidata, mlist): password = cgidata.getvalue('password', '').strip() try: delarchives = int(cgidata.getvalue('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, 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, e: if e.errno not in (errno.EACCES, errno.EPERM): raise problems += 1 syslog('error', 'directory %s not deleted due to permission problems', dir)
def processUnixMailbox(self, input, start=None, end=None): mbox = ArchiverMailbox(input, self.maillist) if start is None: start = 0 counter = 0 if start: mbox.skipping(True) while counter < start: try: m = mbox.next() except Errors.DiscardMessage: continue if m is None: return counter += 1 if start: mbox.skipping(False) while 1: try: pos = input.tell() m = mbox.next() except Errors.DiscardMessage: continue except Exception: syslog('error', 'uncaught archiver exception at filepos: %s', pos) raise if m is None: break if m == '': # It was an unparseable message continue msgid = m.get('message-id', 'n/a') self.message(_('#%(counter)05d %(msgid)s')) a = self._makeArticle(m, self.sequence) self.sequence += 1 self.add_article(a) if end is not None and counter >= end: break counter += 1
def _update_maps(): # Helper function to fix owner and mode. def fixom(file): # It's not necessary for the non-db file to be S_IROTH, but for # simplicity and compatibility with check_perms, we set it. stat = os.stat(file) if (stat[ST_MODE] & targetmode) != targetmode: os.chmod(file, stat[ST_MODE] | targetmode) dbfile = file + '.db' try: stat = os.stat(dbfile) except OSError as e: if e.errno != errno.ENOENT: raise return if (stat[ST_MODE] & targetmode) != targetmode: os.chmod(dbfile, stat[ST_MODE] | targetmode) user = mm_cfg.MAILMAN_USER if stat[ST_UID] != pwd.getpwnam(user)[2]: uid = pwd.getpwnam(user)[2] gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2] os.chown(dbfile, uid, gid) msg = 'command failed: %s (status: %s, %s)' acmd = mm_cfg.POSTFIX_ALIAS_CMD + ' ' + ALIASFILE status = (os.system(acmd) >> 8) & 0xff if status: errstr = os.strerror(status) syslog('error', msg, acmd, status, errstr) raise RuntimeError(msg % (acmd, status, errstr)) # Fix owner and mode of .db if needed. fixom(ALIASFILE) if os.path.exists(VIRTFILE): vcmd = mm_cfg.POSTFIX_MAP_CMD + ' ' + VIRTFILE status = (os.system(vcmd) >> 8) & 0xff if status: errstr = os.strerror(status) syslog('error', msg, vcmd, status, errstr) raise RuntimeError(msg % (vcmd, status, errstr)) # Fix owner and mode of .db if needed. fixom(VIRTFILE)
def main(): # print 'Content-type: text/plain\n\n' print 'Content-type: text/plain; charset=us-ascii\n' doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.SetTitle(_("Private Archive Error")) doc.AddItem(Header(3, _("You must specify a list."))) print doc.Format() return path = os.environ.get('PATH_INFO') tpath = true_path(path) if tpath <> path[1:]: msg = _('Private archive - "./" and "../" not allowed in URL.') doc.SetTitle(msg) doc.AddItem(Header(2, msg)) print doc.Format() syslog('mischief', 'Private archive hostile path: %s', path) return # BAW: This needs to be converted to the Site module abstraction true_filename = os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) msg = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(_("Private Archive Error - %(msg)s")) doc.AddItem(Header(2, msg)) print doc.Format() syslog('error', 'No such list "%s": %s\n', listname, e) return
def main(): # Trick out pygettext since we want to mark template_data as translatable, # but we don't want to actually translate it here. def _(s): return s template_data = ( ('listinfo.html', _('General list information page')), ('subscribe.html', _('Subscribe results page')), ('options.html', _('User specific options page')), ('subscribeack.txt', _('Welcome email text file')), ('masthead.txt', _('Digest masthead')), ) _ = i18n._ doc = Document() # Set up the system default language i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.AddItem(Header(2, _("List name is required."))) print doc.Format() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>'))) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'No such list "%s": %s', listname, e) return
def _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 handler (self): listname = self.cgidata.getvalue('list') action = self.cgidata.getvalue('action').lower().strip() syslog('sso', 'User: %s; Listname: %s; action: %s' % (self.curr_user, listname, action)) mlist = MailList.MailList(listname) userdesc = UserDesc(self.curr_user, u'', mm_cfg.SSO_STOCK_USER_PWD, False) if action == 'join': try: text = ('Welcome to %s. Visit the List Server to ' + 'manage your subscriptions') % listname mlist.ApprovedAddMember(userdesc, True, text, whence='SSO Web Interface') mlist.Save() self.kwargs_add('notice_success', True) self.kwargs_add('notice_text', 'Successfully added to list: %s' % listname) except Errors.MMAlreadyAMember: self.kwargs_add('notice_success', False) self.kwargs_add('notice_text', 'You are already subscribed to %s' % listname) elif action == 'leave': try: mlist.ApprovedDeleteMember(self.curr_user) mlist.Save() self.kwargs_add('notice_success', True) self.kwargs_add('notice_text', 'Successfully removed from list: %s' % listname) except Errors.NotAMemberError: # User has already been unsubscribed self.kwargs_add('notice_success', False) self.kwargs_add('notice_text', 'You are not a member of %s' % listname) self.render()
def IsDMARCProhibited(mlist, email): if not dns_resolver: return False email = email.lower() at_sign = email.find('@') if at_sign < 1: return False dmarc_domain = '_dmarc.' + email[at_sign + 1:] try: resolver = dns.resolver.Resolver() resolver.timeout = float(mm_cfg.DMARC_RESOLVER_TIMEOUT) resolver.lifetime = float(mm_cfg.DMARC_RESOLVER_LIFETIME) txt_recs = resolver.query(dmarc_domain, dns.rdatatype.TXT) except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): return False except DNSException, e: syslog('error', 'DNSException: Unable to query DMARC policy for %s (%s). %s', email, dmarc_domain, e.__class__) return False
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 _check_list_actions(self): """See if there are any list actions to perform.""" try: actions = self._proxy.getPendingActions() 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 actions: syslog('xmlrpc', 'Received these actions: %s', COMMASPACE.join(actions)) else: return # There are three actions that can currently be taken. A create # action creates a mailing list, possibly with some defaults, a modify # changes the settings on some existing mailing list, and a deactivate # means that the list should be deactivated. This latter doesn't have # a directly corresponding semantic at the Mailman layer -- if a # mailing list exists, it's activated. We'll take it to mean that the # list should be deleted, but its archives should remain. statuses = {} if 'create' in actions: self._create_or_reactivate(actions['create'], statuses) del actions['create'] if 'modify' in actions: self._modify(actions['modify'], statuses) del actions['modify'] if 'deactivate' in actions: self._deactivate(actions['deactivate'], statuses) del actions['deactivate'] if 'unsynchronized' in actions: self._resynchronize(actions['unsynchronized'], statuses) del actions['unsynchronized'] # Any other keys should be ignored because they specify actions that # we know nothing about. We'll log them to Mailman's log files # though. if actions: syslog('xmlrpc', 'Invalid xmlrpc action keys: %s', COMMASPACE.join(actions)) # Report the statuses to Launchpad. Do this individually so as to # reduce the possibility that a bug in Launchpad causes the reporting # all subsequent mailing lists statuses to fail. The reporting of # status triggers synchronous operations in Launchpad, such as # notifying team admins that their mailing list is ready, and those # operations could fail for spurious reasons. That shouldn't affect # the status reporting for any other list. This is a little more # costly, but it's not that bad. for team_name, (action, status) in statuses.items(): this_status = {team_name: status} try: self._proxy.reportStatus(this_status) syslog('xmlrpc', '[%s] %s: %s' % (team_name, action, status)) except (xmlrpclib.ProtocolError, socket.error) as error: log_exception('Cannot talk to Launchpad:\n%s', error) except xmlrpclib.Fault as error: log_exception('Launchpad exception: %s', error)
def process(mlist, msg, msgdata): """Check the message size (approximately) against hard and soft limits. If the message is below the soft limit, do nothing. This allows the message to be posted without moderation, assuming no other handler get triggered of course. Messages between the soft and hard limits get moderated in the Launchpad web u/i, just as non-member posts would. Personal standing does not override the size checks. Messages above the hard limit get logged and discarded. In production, we should never actually hit the hard limit. The Exim in front of lists.launchpad.net has its own hard limit of 50MB (which is the production standard Mailman hard limit value), so messages larger than this should never even show up. """ # Calculate the message size by turning it back into a string. In Mailman # 3.0 this calculation is done on initial message parse so it will be # quicker and not consume so much memory. But since the hard limit is # 50MB I don't think we can actually get into any real trouble here, as # long as we can trust Python's reference counter. message_size = len(msg.as_string()) # Hard and soft limits are specified in bytes. if message_size < mm_cfg.LAUNCHPAD_SOFT_MAX_SIZE: # Nothing to do. return if message_size < mm_cfg.LAUNCHPAD_HARD_MAX_SIZE: # Hold the message in Mailman. See lpmoderate.py for similar # algorithm. # There is a limit to the size that can be stored in Lp. Send # a trucated copy of the message that has enough information for # the moderator to make a decision. hold(mlist, truncated_message(msg), msgdata, 'Too big') # The message is larger than the hard limit, so log and discard. syslog('vette', 'Discarding message w/size > hard limit: %s', msg.get('message-id', 'n/a')) raise Errors.DiscardMessage
def process(mlist, msg, msgdata): if msgdata.get('approved'): return score, symbols = check_message(mlist, str(msg)) if MEMBER_BONUS != 0: for sender in msg.get_senders(): if mlist.isMember(sender) or \ mlist.GetPattern(sender, mlist.accept_these_nonmembers, at_list='accept_these_nonmembers'): score -= MEMBER_BONUS break if score > DISCARD_SCORE: listname = mlist.real_name sender = msg.get_sender() syslog('vette', '%s post from %s discarded: ' 'SpamAssassin score was %g (discard threshold is %g)' % (listname, sender, score, DISCARD_SCORE)) raise SpamAssassinDiscard elif score > HOLD_SCORE: Hold.hold_for_approval(mlist, msg, msgdata, SpamAssassinHold(score, symbols))
def IsDMARCProhibited(mlist, email): if not dns_resolver: # This is a problem; log it. syslog('error', 'DNS lookup for dmarc_moderation_action for list %s not available', mlist.real_name) return False email = email.lower() # Scan from the right in case quoted local part has an '@'. at_sign = email.rfind('@') if at_sign < 1: return False f_dom = email[at_sign+1:] x = _DMARCProhibited(mlist, email, '_dmarc.' + f_dom) if x != 'continue': return x o_dom = get_org_dom(f_dom) if o_dom != f_dom: x = _DMARCProhibited(mlist, email, '_dmarc.' + o_dom, org=True) if x != 'continue': return x return False
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 quick_maketext(templatefile, dict=None, lang=None, mlist=None): if mlist is None: listname = '' else: listname = mlist._internal_name if lang is None: if mlist is None: lang = mm_cfg.DEFAULT_SERVER_LANGUAGE else: lang = mlist.preferred_language cachekey = (templatefile, lang, listname) filepath = _templatefilepathcache.get(cachekey) if filepath: template = _templatecache.get(filepath) if filepath is None or template is None: # Use the basic maketext, with defaults to get the raw template template, filepath = Utils.findtext(templatefile, lang=lang, raw=True, mlist=mlist) _templatefilepathcache[cachekey] = filepath _templatecache[filepath] = template # Copied from Utils.maketext() text = template if dict is not None: try: sdict = SafeDict(dict) try: text = sdict.interpolate(template) except UnicodeError: # Try again after coercing the template to unicode utemplate = unicode(template, Utils.GetCharSet(lang), 'replace') text = sdict.interpolate(utemplate) except (TypeError, ValueError), e: # The template is really screwed up syslog('error', 'broken template: %s\n%s', filepath, e)
def __init__(self, mlist): self.__mlist = mlist self._dbconnect() # define the table and standard condition reflecting listname # (this is for upwards compatibility with the 'wide' mode and # the formerly fixed name of database table in 'flat' mode) if self.getTableType() is 'flat': try: self._table = mm_cfg.MYSQL_MEMBER_TABLE_NAME except AttributeError: self._table = 'mailman_mysql' self._where = "listname = '%s'" %(self.__mlist.internal_name()) else: self._table = self.__mlist.internal_name() self._where = '1=1' # Make sure we always have the table we need... # if mm_cfg.MYSQL_MEMBER_CREATE_TABLE was defined try: if mm_cfg.MYSQL_MEMBER_CREATE_TABLE: self.createTable() except AttributeError: pass if mm_cfg.MYSQL_MEMBER_DB_VERBOSE: # Message to indicate successful init. message = "MysqlMemberships " \ + "$Revision: 1.69 $ initialized with host: %s (%s)" % ( mm_cfg.connection.get_host_info(), mm_cfg.connection.get_server_info() ) syslog('error', message) syslog('mysql', message) # add a cache memory self._cache = {} self._cachedate = 0
def main(): parts = Utils.GetPathPieces() if not parts: listinfo_overview() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') listinfo_overview(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'listinfo: No such list "%s": %s', listname, e) return # See if the user want to see this page in other language cgidata = cgi.FieldStorage() try: language = cgidata.getfirst('language') except TypeError: # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) # Send this with a 400 status. print('Status: 400 Bad Request') print(doc.Format()) return if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) list_listinfo(mlist, language)
def process(mlist, msg, msgdata): # Short circuit non-digestable lists. if not mlist.digestable or msgdata.get('isdigest'): return mboxfile = os.path.join(mlist.fullpath(), 'digest.mbox') omask = os.umask(007) try: mboxfp = open(mboxfile, 'a+') finally: os.umask(omask) mbox = Mailbox(mboxfp) mbox.AppendMessage(msg) # Calculate the current size of the accumulation file. This will not tell # us exactly how big the MIME, rfc1153, or any other generated digest # message will be, but it's the most easily available metric to decide # whether the size threshold has been reached. mboxfp.flush() size = os.path.getsize(mboxfile) if (mlist.digest_size_threshhold > 0 and size / 1024.0 >= mlist.digest_size_threshhold): # This is a bit of a kludge to get the mbox file moved to the digest # queue directory. try: # Enclose in try/except here because a error in send_digest() can # silently stop regular delivery. Unsuccessful digest delivery # should be tried again by cron and the site administrator will be # notified of any error explicitly by the cron error message. mboxfp.seek(0) send_digests(mlist, mboxfp) os.unlink(mboxfile) except Exception, errmsg: # Bare except is generally prohibited in Mailman, but we can't # forecast what exceptions can occur here. syslog('error', 'send_digests() failed: %s', errmsg) s = StringIO() traceback.print_exc(file=s) syslog('error', s.getvalue())
def _dbconnect(self): if mm_cfg.connection: try: if mm_cfg.connection.ping() == 0: return mm_cfg.connection except: syslog('mysql', 'connection warning') pass # Connection failed, or an error, try a hard dis+reconnect. try: mm_cfg.cursor.close() except: syslog('error', 'error on mm_cfg.cursor.close()') pass try: mm_cfg.connection.close() except: syslog('error', 'error on mm_cfg.connection.close()') pass try: mm_cfg.connection = MySQLdb.connect( passwd=mm_cfg.MYSQL_MEMBER_DB_PASS, db=mm_cfg.MYSQL_MEMBER_DB_NAME, user=mm_cfg.MYSQL_MEMBER_DB_USER, host=mm_cfg.MYSQL_MEMBER_DB_HOST) mm_cfg.cursor = mm_cfg.connection.cursor() except MySQLdb.OperationalError, e: message = "Error connecting to MySQL database %s (%s): %s" % ( mm_cfg.MYSQL_MEMBER_DB_NAME, e.args[0], e.args[1]) syslog('error', message) if mm_cfg.MYSQL_MEMBER_DB_VERBOSE: syslog('mysql', message) # exit? why not sleep(30) and retry? sys.exit(1)
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 to_plaintext(msg): changedp = 0 for subpart in typed_subpart_iterator(msg, 'text', 'html'): filename = tempfile.mktemp('.html') fp = open(filename, 'w') try: fp.write(subpart.get_payload(decode=1)) fp.close() cmd = os.popen(mm_cfg.HTML_TO_PLAIN_TEXT_COMMAND % {'filename': filename}) plaintext = cmd.read() rtn = cmd.close() if rtn: syslog('error', 'HTML->text/plain error: %s', rtn) finally: try: os.unlink(filename) except OSError, e: if e.errno <> errno.ENOENT: raise # Now replace the payload of the subpart and twiddle the Content-Type: del subpart['content-transfer-encoding'] subpart.set_payload(plaintext) subpart.set_type('text/plain') changedp = 1
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts or len(parts) < 1: bad_confirmation(doc) doc.AddItem(MailmanLogo()) print doc.Format() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) bad_confirmation(doc, _('No such list <em>%(safelistname)s</em>')) doc.AddItem(MailmanLogo()) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'confirm: No such list "%s": %s', listname, e) return
def __init__(self, slice=None, numslices=None): """Create a faux runner which checks into Launchpad occasionally. Every XMLRPC_SLEEPTIME number of seconds, this runner wakes up and connects to a Launchpad XMLRPC service to see if there's anything for it to do. slice and numslices are ignored, but required by the Mailman queue runner framework. """ self.SLEEPTIME = mm_cfg.XMLRPC_SLEEPTIME # Instead of calling the superclass's __init__() method, just # initialize the two attributes that are actually used. The reason # for this is that the XMLRPCRunner doesn't have a queue so it # shouldn't be trying to create a Switchboard instance. Still, it # needs a dummy _kids and _stop attributes for the rest of the runner # to work. We're using runners in a more general sense than Mailman 2 # is designed for. self._kids = {} self._stop = False self._proxy = get_mailing_list_api_proxy() # Ensure that the log file exists, mostly for the test suite. syslog('xmlrpc', 'XMLRPC runner starting') self.heartbeat_frequency = timedelta(minutes=5) self.last_heartbeat = None self._heartbeat()
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) cgidata = cgi.FieldStorage() parts = Utils.GetPathPieces() if not parts: # Bad URL specification title = _('Bad URL specification') doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print doc.Format() syslog('error', 'Bad URL specification: %s', parts) return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) title = _('No such list <em>%(safelistname)s</em>') doc.SetTitle(_('No such list %(safelistname)s')) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'rmlist: No such list "%s": %s\n', listname, e) return
def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) parts = Utils.GetPathPieces() if not parts: doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script'))) print doc.Format() return listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('No such list <em>%(safelistname)s</em>'))) # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() syslog('error', 'subscribe: No such list "%s": %s\n', listname, e) return
def decorate(mlist, template, what, extradict=None): # `what' is just a descriptive phrase used in the log message # If template is only whitespace, ignore it. if len(re.sub('\s', '', template)) == 0: return '' # BAW: We've found too many situations where Python can be fooled into # interpolating too much revealing data into a format string. For # example, a footer of "% silly %(real_name)s" would give a header # containing all list attributes. While we've previously removed such # really bad ones like `password' and `passwords', it's much better to # provide a whitelist of known good attributes, then to try to remove a # blacklist of known bad ones. d = SafeDict({'real_name' : mlist.real_name, 'list_name' : mlist.internal_name(), # For backwards compatibility '_internal_name': mlist.internal_name(), 'host_name' : mlist.host_name, 'web_page_url' : mlist.web_page_url, 'description' : mlist.description, 'info' : mlist.info, 'cgiext' : mm_cfg.CGIEXT, }) if extradict is not None: d.update(extradict) # Using $-strings? if getattr(mlist, 'use_dollar_strings', 0): template = Utils.to_percent(template) # Interpolate into the template try: text = re.sub(r'(?m)(?<!^--) +(?=\n)', '', re.sub(r'\r\n', r'\n', template % d)) except (ValueError, TypeError), e: syslog('error', 'Exception while calculating %s:\n%s', what, e) text = template
def _dispose(self, mlist, msg, msgdata): # The policy here is similar to the Replybot policy. If a message has # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard # it to prevent replybot response storms. precedence = msg.get('precedence', '').lower() ack = msg.get('x-ack', '').lower() if ack <> 'yes' and precedence in ('bulk', 'junk', 'list'): syslog('vette', 'Precedence: %s message discarded by: %s', precedence, mlist.GetRequestEmail()) return False # Do replybot for commands mlist.Load() Replybot.process(mlist, msg, msgdata) if mlist.autorespond_requests == 1: syslog('vette', 'replied and discard') # w/discard return False # Now craft the response res = Results(mlist, msg, msgdata) # BAW: Not all the functions of this qrunner require the list to be # locked. Still, it's more convenient to lock it here and now and # deal with lock failures in one place. try: mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) except LockFile.TimeOutError: # Oh well, try again later return True # This message will have been delivered to one of mylist-request, # mylist-join, or mylist-leave, and the message metadata will contain # a key to which one was used. try: ret = BADCMD if msgdata.get('torequest'): ret = res.process() elif msgdata.get('tojoin'): ret = res.do_command('join') elif msgdata.get('toleave'): ret = res.do_command('leave') elif msgdata.get('toconfirm'): mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', '')) if mo: ret = res.do_command('confirm', (mo.group('cookie'), )) if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: syslog('vette', 'No command, message discarded, msgid: %s', msg.get('message-id', 'n/a')) else: res.send_response() mlist.Save() finally: mlist.Unlock()
def _dopipeline(self, mlist, msg, msgdata, pipeline): while pipeline: handler = pipeline.pop(0) modname = 'Mailman.Handlers.' + handler __import__(modname) try: pid = os.getpid() sys.modules[modname].process(mlist, msg, msgdata) # Failsafe -- a child may have leaked through. if pid != os.getpid(): syslog('error', 'child process leaked thru: %s', modname) os._exit(1) except Errors.DiscardMessage: # Throw the message away; we need do nothing else with it. # We do need to push the current handler back in the pipeline # just in case the syslog call throws an exception and the # message is shunted. pipeline.insert(0, handler) syslog( 'vette', """Message discarded, msgid: %s' list: %s, handler: %s""", msg.get('message-id', 'n/a'), mlist.real_name, handler) return 0 except Errors.HoldMessage: # Let the approval process take it from here. The message no # longer needs to be queued. return 0 except Errors.RejectMessage as e: # Log this. # We do need to push the current handler back in the pipeline # just in case the syslog call or BounceMessage throws an # exception and the message is shunted. pipeline.insert(0, handler) syslog( 'vette', """Message rejected, msgid: %s list: %s, handler: %s, reason: %s""", msg.get('message-id', 'n/a'), mlist.real_name, handler, e.notice()) mlist.BounceMessage(msg, msgdata, e) return 0 except: # Push this pipeline module back on the stack, then re-raise # the exception. pipeline.insert(0, handler) raise # We've successfully completed handling of this message return 0
def do_exclude(mlist, msg, msgdata, recips): # regular_exclude_lists are the other mailing lists on this mailman # installation whose members are excluded from the regular (non-digest) # delivery of this list if those list addresses appear in To: or Cc: # headers. if not mlist.regular_exclude_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_exclude_lists: listname = listname.lower() if listname not in destinations: continue listlhs, hostname = listname.split('@') if listlhs == mlist.internal_name(): syslog('error', 'Exclude list %s is a self reference.', listname) continue try: slist = MailList(listlhs, lock=False) except MMUnknownListError: syslog('error', 'Exclude list %s not found.', listname) continue if not mm_cfg.ALLOW_CROSS_DOMAIN_SIBLING \ and slist.host_name != hostname: syslog('error', 'Exclude list %s is not in the same domain.', listname) continue if mlist.regular_exclude_ignore: for sender in msg.get_senders(): if slist.isMember(sender): break for sender in Utils.check_eq_domains(sender, slist.equivalent_domains): if slist.isMember(sender): break if slist.isMember(sender): break else: continue srecips = set([slist.getMemberCPAddress(m) for m in slist.getRegularMemberKeys() if slist.getDeliveryStatus(m) == ENABLED]) recips -= srecips return list(recips)
def log_exception(message, *args): """Write the current exception traceback into the Mailman log file. This is really just a convenience function for a refactored chunk of common code. :param message: The message to appear in the xmlrpc and error logs. It may be a format string. :param args: Optional arguments to be interpolated into a format string. """ error_utility = ErrorReportingUtility() error_utility.configure(section_name='mailman') error_utility.raising(sys.exc_info()) out_file = StringIO() traceback.print_exc(file=out_file) traceback_text = out_file.getvalue() syslog('xmlrpc', message, *args) syslog('error', message, *args) syslog('error', traceback_text)