def process_message(self, peer, mailfrom, rcpttos, data): from io import StringIO from Mailman import Utils from Mailman import Message from Mailman import MailList # If the message is to a Mailman mailing list, then we'll invoke the # Mailman script directly, without going through the real smtpd. # Otherwise we'll forward it to the local proxy for disposition. listnames = [] for rcpt in rcpttos: local = rcpt.lower().split('@')[0] # We allow the following variations on the theme # listname # listname-admin # listname-owner # listname-request # listname-join # listname-leave parts = local.split('-') if len(parts) > 2: continue listname = parts[0] if len(parts) == 2: command = parts[1] else: command = '' if not Utils.list_exists(listname) or command not in ( '', 'admin', 'owner', 'request', 'join', 'leave'): continue listnames.append((rcpt, listname, command)) # Remove all list recipients from rcpttos and forward what we're not # going to take care of ourselves. Linear removal should be fine # since we don't expect a large number of recipients. for rcpt, listname, command in listnames: rcpttos.remove(rcpt) # If there's any non-list destined recipients left, print('forwarding recips:', ' '.join(rcpttos), file=DEBUGSTREAM) if rcpttos: refused = self._deliver(mailfrom, rcpttos, data) # TBD: what to do with refused addresses? print('we got refusals:', refused, file=DEBUGSTREAM) # Now deliver directly to the list commands mlists = {} s = StringIO(data) msg = Message.Message(s) # These headers are required for the proper execution of Mailman. All # MTAs in existence seem to add these if the original message doesn't # have them. if not msg.get('from'): msg['From'] = mailfrom if not msg.get('date'): msg['Date'] = time.ctime(time.time()) for rcpt, listname, command in listnames: print('sending message to', rcpt, file=DEBUGSTREAM) mlist = mlists.get(listname) if not mlist: mlist = MailList.MailList(listname, lock=0) mlists[listname] = mlist # dispatch on the type of command if command == '': # post msg.Enqueue(mlist, tolist=1) elif command == 'admin': msg.Enqueue(mlist, toadmin=1) elif command == 'owner': msg.Enqueue(mlist, toowner=1) elif command == 'request': msg.Enqueue(mlist, torequest=1) elif command in ('join', 'leave'): # TBD: this is a hack! if command == 'join': msg['Subject'] = 'subscribe' else: msg['Subject'] = 'unsubscribe' msg.Enqueue(mlist, torequest=1)
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 as 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 i18n.set_language(mlist.preferred_language) doc.set_language(mlist.preferred_language) cgidata = cgi.FieldStorage() try: username = cgidata.getfirst('username', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. 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 password = cgidata.getfirst('password', '') is_auth = 0 realname = mlist.real_name message = '' if not mlist.WebAuthenticate((mm_cfg.AuthUser, mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), password, username): if 'submit' in cgidata: # This is a re-authorization attempt message = Bold(FontSize('+1', _('Authorization failed.'))).Format() remote = os.environ.get('HTTP_FORWARDED_FOR', os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) syslog('security', 'Authorization failed (private): user=%s: list=%s: remote=%s', username, listname, remote) # give an HTTP 401 for authentication failure print('Status: 401 Unauthorized') # Are we processing a password reminder from the login screen? if 'login-remind' in cgidata: if username: message = Bold(FontSize('+1', _("""If you are a list member, your password has been emailed to you."""))).Format() else: message = Bold(FontSize('+1', _('Please enter your email address'))).Format() if mlist.isMember(username): mlist.MailUserPassword(username) elif username: # Not a member if mlist.private_roster == 0: # Public rosters safeuser = Utils.websafe(username) message = Bold(FontSize('+1', _('No such member: %(safeuser)s.'))).Format() else: syslog('mischief', 'Reminder attempt of non-member w/ private rosters: %s', username) # Output the password form charset = Utils.GetCharSet(mlist.preferred_language) print('Content-type: text/html; charset=' + charset + '\n\n') # Put the original full path in the authorization form, but avoid # trailing slash if we're not adding parts. We add it below. action = mlist.GetScriptURL('private', absolute=1) if mboxfile: action += '.mbox' if parts[1:]: action = os.path.join(action, SLASH.join(parts[1:])) # If we added '/index.html' to true_filename, add a slash to the URL. # We need this because we no longer add the trailing slash in the # private.html template. It's always OK to test parts[-1] since we've # already verified parts[0] is listname. The basic rule is if the # post URL (action) is a directory, it must be slash terminated, but # not if it's a file. Otherwise, relative links in the target archive # page don't work. if true_filename.endswith('/index.html') and parts[-1] != 'index.html': action += SLASH # Escape web input parameter to avoid cross-site scripting. print(Utils.maketext( 'private.html', {'action' : Utils.websafe(action), 'realname': mlist.real_name, 'message' : message, }, mlist=mlist)) return lang = mlist.getMemberLanguage(username) i18n.set_language(lang) doc.set_language(lang) # Authorization confirmed... output the desired file try: ctype, enc = guess_type(path, strict=0) if ctype is None: ctype = 'text/html' if mboxfile: f = open(os.path.join(mlist.archive_dir() + '.mbox', mlist.internal_name() + '.mbox')) ctype = 'text/plain' elif true_filename.endswith('.gz'): import gzip f = gzip.open(true_filename, 'r') else: f = open(true_filename, 'r') except IOError: msg = _('Private archive file not found') doc.SetTitle(msg) doc.AddItem(Header(2, msg)) print('Status: 404 Not Found') print(doc.Format()) syslog('error', 'Private archive file not found: %s', true_filename) else: print('Content-type: %s\n' % ctype) sys.stdout.write(f.read()) f.close()
from Mailman.ListAdmin import readMessage from email.Iterators import typed_subpart_iterator uid = getpwnam(mm_cfg.MAILMAN_USER)[2] gid = getgrnam(mm_cfg.MAILMAN_GROUP)[2] if not os.getuid(): os.setregid(gid, gid) os.setreuid(uid, uid) if (os.getuid() is not uid) or (os.getgid() is not gid): sys.exit(0) for listname in Utils.list_names(): try: mlist = MailList.MailList(listname, lock=0) except: print 'ERROR for ' + listname continue try: mlist.Lock() ############################################ # do treatement here ############################################ mlist.Save() mlist.Unlock() print 'OK for ' + listname except: print 'ERROR for ' + listname
def change_list_attribute(self, attribute, value): mlist = MailList.MailList(self.list_name) setattr(mlist, attribute, value) mlist.Save() mlist.Unlock()
def get_mailinglist(listname, lock=True): try: return MailList.MailList(listname, lock=lock) except Errors.MMUnknownListError: raise jsonify("Unknown Mailing List `{}`.".format(listname), 404)
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 as 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 # See if the form data has a preferred language set, in which case, use it # for the results. If not, use the list's preferred language. cgidata = cgi.FieldStorage() try: language = cgidata.getfirst('language', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. 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) doc.set_language(language) # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. # # BAW: Strictly speaking, the list should not need to be locked just to # read the request database. However the request database asserts that # the list is locked in order to load it and it's not worth complicating # that logic. def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() # ...and ensure we exit, otherwise race conditions could cause us to # enter MailList.Save() while we're in the unlocked state, and that # could be bad! sys.exit(0) mlist.Lock() try: # Install the emergency shutdown signal handler signal.signal(signal.SIGTERM, sigterm_handler) process_form(mlist, doc, cgidata, language) mlist.Save() finally: mlist.Unlock()
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: try: mlist = MailList.MailList(name, lock=0) except Errors.MMUnknownListError: # The list could have been deleted by another process. continue 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 main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) cgidata = cgi.FieldStorage() try: cgidata.getfirst('password', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. 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 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 as 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 # Now that we have a valid mailing list, set the language i18n.set_language(mlist.preferred_language) doc.set_language(mlist.preferred_language) # Be sure the list owners are not sneaking around! if not mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS: title = _("You're being a sneaky list owner!") doc.SetTitle(title) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) syslog('mischief', 'Attempt to sneakily delete a list: %s', listname) return if 'doit' in cgidata: process_request(doc, cgidata, mlist) print(doc.Format()) return request_deletion(doc, mlist) # Always add the footer and print the document doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format())
def process_request(doc, cgidata): # Lowercase the listname since this is treated as the "internal" name. listname = cgidata.getfirst('listname', '').strip().lower() owner = cgidata.getfirst('owner', '').strip() try: autogen = int(cgidata.getfirst('autogen', '0')) except ValueError: autogen = 0 try: notify = int(cgidata.getfirst('notify', '0')) except ValueError: notify = 0 try: moderate = int(cgidata.getfirst('moderate', mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) except ValueError: moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION password = cgidata.getfirst('password', '').strip() confirm = cgidata.getfirst('confirm', '').strip() auth = cgidata.getfirst('auth', '').strip() langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) if not isinstance(langs, list): langs = [langs] # Sanity check safelistname = Utils.websafe(listname) if '@' in listname: request_creation(doc, cgidata, _('List name must not include "@": %(safelistname)s')) return if Utils.list_exists(listname): # BAW: should we tell them the list already exists? This could be # used to mine/guess the existance of non-advertised lists. Then # again, that can be done in other ways already, so oh well. request_creation(doc, cgidata, _('List already exists: %(safelistname)s')) return if not listname: request_creation(doc, cgidata, _('You forgot to enter the list name')) return if not owner: request_creation(doc, cgidata, _('You forgot to specify the list owner')) return if autogen: if password or confirm: request_creation( doc, cgidata, _('''Leave the initial password (and confirmation) fields blank if you want Mailman to autogenerate the list passwords.''')) return password = confirm = Utils.MakeRandomPassword( mm_cfg.ADMIN_PASSWORD_LENGTH) else: if password != confirm: request_creation(doc, cgidata, _('Initial list passwords do not match')) return if not password: request_creation( doc, cgidata, # The little <!-- ignore --> tag is used so that this string # differs from the one in bin/newlist. The former is destined # for the web while the latter is destined for email, so they # must be different entries in the message catalog. _('The list password cannot be empty<!-- ignore -->')) return # The authorization password must be non-empty, and it must match either # the list creation password or the site admin password ok = 0 if auth: ok = Utils.check_global_password(auth, 0) if not ok: ok = Utils.check_global_password(auth) if not ok: request_creation( doc, cgidata, _('You are not authorized to create new mailing lists')) return # Make sure the web hostname matches one of our virtual domains hostname = Utils.get_domain() if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ hostname not in mm_cfg.VIRTUAL_HOSTS: safehostname = Utils.websafe(hostname) request_creation(doc, cgidata, _('Unknown virtual host: %(safehostname)s')) return emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST) # We've got all the data we need, so go ahead and try to create the list # See admin.py for why we need to set up the signal handler. mlist = MailList.MailList() def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() # ...and ensure we exit, otherwise race conditions could cause us to # enter MailList.Save() while we're in the unlocked state, and that # could be bad! sys.exit(0) try: # Install the emergency shutdown signal handler signal.signal(signal.SIGTERM, sigterm_handler) pw = sha_new(password.encode()).hexdigest() # Guarantee that all newly created files have the proper permission. # proper group ownership should be assured by the autoconf script # enforcing that all directories have the group sticky bit set oldmask = os.umask(0o02) try: try: mlist.Create(listname, owner, pw, langs, emailhost, urlhost=hostname) finally: os.umask(oldmask) except Errors.EmailAddressError as e: if e.args: s = Utils.websafe(e.args[0]) else: s = Utils.websafe(owner) request_creation(doc, cgidata, _('Bad owner email address: %(s)s')) return except Errors.MMListAlreadyExistsError: # MAS: List already exists so we don't need to websafe it. request_creation(doc, cgidata, _('List already exists: %(listname)s')) return except Errors.BadListNameError as e: if e.args: s = Utils.websafe(e.args[0]) else: s = Utils.websafe(listname) request_creation(doc, cgidata, _('Illegal list name: %(s)s')) return except Errors.MMListError: request_creation( doc, cgidata, _('''Some unknown error occurred while creating the list. Please contact the site administrator for assistance.''')) return # Initialize the host_name and web_page_url attributes, based on # virtual hosting settings and the request environment variables. mlist.default_member_moderation = moderate mlist.web_page_url = mm_cfg.DEFAULT_URL_PATTERN % hostname mlist.host_name = emailhost mlist.Save() 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. mlist.Unlock() # Now do the MTA-specific list creation tasks if mm_cfg.MTA: modname = 'Mailman.MTA.' + mm_cfg.MTA __import__(modname) sys.modules[modname].create(mlist, cgi=1) # And send the notice to the list owner. if notify: siteowner = Utils.get_site_email(mlist.host_name, 'owner') text = Utils.maketext( 'newlist.txt', {'listname' : listname, 'password' : password, 'admin_url' : mlist.GetScriptURL('admin', absolute=1), 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), 'requestaddr' : mlist.GetRequestEmail(), 'siteowner' : siteowner, }, mlist=mlist) msg = Message.UserNotification( owner, siteowner, _('Your new mailing list: %(listname)s'), text, mlist.preferred_language) msg.send(mlist) # Success! listinfo_url = mlist.GetScriptURL('listinfo', absolute=1) admin_url = mlist.GetScriptURL('admin', absolute=1) create_url = Utils.ScriptURL('create') title = _('Mailing list creation 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) table.AddRow([_('''You have successfully created the mailing list <b>%(listname)s</b> and notification has been sent to the list owner <b>%(owner)s</b>. You can now:''')]) ullist = UnorderedList() ullist.AddItem(Link(listinfo_url, _("Visit the list's info page"))) ullist.AddItem(Link(admin_url, _("Visit the list's admin page"))) ullist.AddItem(Link(create_url, _('Create another list'))) table.AddRow([ullist]) doc.AddItem(table)
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 as 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 # Set the language for the list i18n.set_language(mlist.preferred_language) doc.set_language(mlist.preferred_language) # Get the form data to see if this is a second-step confirmation cgidata = cgi.FieldStorage(keep_blank_values=1) try: cookie = cgidata.getfirst('cookie') except TypeError: # Someone crafted a POST with a bad Content-Type:. 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 cookie == '': ask_for_cookie(mlist, doc, _('Confirmation string was empty.')) return if not cookie and len(parts) == 2: cookie = parts[1] if len(parts) > 2: bad_confirmation(doc) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return if not cookie: ask_for_cookie(mlist, doc) return days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1) + 0.5) confirmurl = mlist.GetScriptURL('confirm', absolute=1) # Avoid cross-site scripting attacks safecookie = Utils.websafe(cookie) badconfirmstr = _('''<b>Invalid confirmation string:</b> %(safecookie)s. <p>Note that confirmation strings expire approximately %(days)s days after the initial request. They also expire if the request has already been handled in some way. If your confirmation has expired, please try to re-submit your request. Otherwise, <a href="%(confirmurl)s">re-enter</a> your confirmation string.''') content = mlist.pend_confirm(cookie, expunge=False) if content is None: bad_confirmation(doc, badconfirmstr) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return try: if content[0] == Pending.SUBSCRIPTION: if cgidata.getfirst('cancel'): subscription_cancel(mlist, doc, cookie) elif cgidata.getfirst('submit'): subscription_confirm(mlist, doc, cookie, cgidata) else: subscription_prompt(mlist, doc, cookie, content[1]) elif content[0] == Pending.UNSUBSCRIPTION: try: if cgidata.getfirst('cancel'): unsubscription_cancel(mlist, doc, cookie) elif cgidata.getfirst('submit'): unsubscription_confirm(mlist, doc, cookie) else: unsubscription_prompt(mlist, doc, cookie, *content[1:]) except Errors.NotAMemberError: doc.addError( _("""The address requesting unsubscription is not a member of the mailing list. Perhaps you have already been unsubscribed, e.g. by the list administrator?""")) # Expunge this record from the pending database. expunge(mlist, cookie) elif content[0] == Pending.CHANGE_OF_ADDRESS: if cgidata.getfirst('cancel'): addrchange_cancel(mlist, doc, cookie) elif cgidata.getfirst('submit'): addrchange_confirm(mlist, doc, cookie) else: # Watch out for users who have unsubscribed themselves in the # meantime! try: addrchange_prompt(mlist, doc, cookie, *content[1:]) except Errors.NotAMemberError: doc.addError( _("""The address requesting to be changed has been subsequently unsubscribed. This request has been cancelled.""")) # Expunge this record from the pending database. expunge(mlist, cookie) elif content[0] == Pending.HELD_MESSAGE: if cgidata.getfirst('cancel'): heldmsg_cancel(mlist, doc, cookie) elif cgidata.getfirst('submit'): heldmsg_confirm(mlist, doc, cookie) else: heldmsg_prompt(mlist, doc, cookie, *content[1:]) elif content[0] == Pending.RE_ENABLE: if cgidata.getfirst('cancel'): reenable_cancel(mlist, doc, cookie) elif cgidata.getfirst('submit'): reenable_confirm(mlist, doc, cookie) else: reenable_prompt(mlist, doc, cookie, *content[1:]) else: bad_confirmation(doc, _('System error, bad content: %(content)s')) except Errors.MMBadConfirmation: bad_confirmation(doc, badconfirmstr) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format())
def create_list(userdesc, perms, vhost, listname, desc, advertise, modlevel, inslevel, owners, members): """ Create a new list. @root """ name = vhost.lower() + VHOST_SEP + listname.lower() if Utils.list_exists(name): print >> sys.stderr, "List ", name, " already exists" return 0 owner = [] for o in owners: email = to_forlife(o) print >> sys.stderr, "owner in list", o, email email = email[0] if email is not None: owner.append(email) if len(owner) is 0: print >> sys.stderr, "No owner found in ", owners return 0 mlist = MailList.MailList() try: oldmask = os.umask(002) pw = sha.new('foobar').hexdigest() try: mlist.Create(name, owner[0], pw) finally: os.umask(oldmask) mlist.real_name = listname mlist.host_name = 'listes.polytechnique.org' mlist.description = desc mlist.advertised = int(advertise) is 0 mlist.default_member_moderation = int(modlevel) is 2 mlist.generic_nonmember_action = int(modlevel) > 0 mlist.subscribe_policy = 2 * (int(inslevel) is 1) mlist.admin_notify_mchanges = (mlist.subscribe_policy or mlist.generic_nonmember_action or mlist.default_member_moderation or not mlist.advertised) mlist.owner = owner mlist.subject_prefix = '[' + listname + '] ' mlist.max_message_size = 0 inverted_listname = listname.lower() + '_' + vhost.lower() mlist.msg_footer = "_______________________________________________\n" \ + "Liste de diffusion %(real_name)s\n" \ + "http://listes.polytechnique.org/members/" + inverted_listname mlist.header_filter_rules = [] mlist.header_filter_rules.append( ('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False)) mlist.header_filter_rules.append( ('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False)) if ON_CREATE_CMD != '': try: os.system(ON_CREATE_CMD + ' ' + name) except: pass check_options_runner(userdesc, perms, mlist, listname.lower(), True) mass_subscribe(userdesc, perms, mlist, members) mlist.Save() finally: mlist.Unlock() # avoid the "-1 mail to moderate" bug mlist = MailList.MailList(name) try: mlist._UpdateRecords() mlist.Save() finally: mlist.Unlock() return 1
def main(): parts = Utils.GetPathPieces() if not parts: error_page(_('Invalid options to CGI script')) 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') error_page(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'roster: No such list "%s": %s', listname, e) return cgidata = cgi.FieldStorage() # messages in form should go in selected language (if any...) try: lang = 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(lang): lang = mlist.preferred_language i18n.set_language(lang) # Perform authentication for protected rosters. If the roster isn't # protected, then anybody can see the pages. If members-only or # "admin"-only, then we try to cookie authenticate the user, and failing # that, we check roster-email and roster-pw fields for a valid password. # (also allowed: the list moderator, the list admin, and the site admin). password = cgidata.getfirst('roster-pw', '').strip() addr = cgidata.getfirst('roster-email', '').strip() list_hidden = (not mlist.WebAuthenticate( (mm_cfg.AuthUser, ), password, addr) and mlist.WebAuthenticate( (mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), password)) if mlist.private_roster == 0: # No privacy ok = 1 elif mlist.private_roster == 1: # Members only ok = mlist.WebAuthenticate( (mm_cfg.AuthUser, mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), password, addr) else: # Admin only, so we can ignore the address field ok = mlist.WebAuthenticate( (mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), password) if not ok: realname = mlist.real_name doc = Document() doc.set_language(lang) # Send this with a 401 status. print('Status: 401 Unauthorized') error_page_doc(doc, _('%(realname)s roster authentication failed.')) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) remote = os.environ.get( 'HTTP_FORWARDED_FOR', os.environ.get( 'HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) syslog('security', 'Authorization failed (roster): list=%s: remote=%s', listname, remote) return # The document and its language doc = HeadlessDocument() doc.set_language(lang) replacements = mlist.GetAllReplacements(lang, list_hidden) replacements['<mm-displang-box>'] = mlist.FormatButton( 'displang-button', text=_('View this page in')) replacements['<mm-lang-form-start>'] = mlist.FormatFormStart('roster') doc.AddItem(mlist.ParseTags('roster.html', replacements, lang)) print(doc.Format())
def process_request(doc, cgidata): # Lowercase the listname since this is treated as the "internal" name. listname = cgidata.getvalue('listname', '').strip().lower() owner = cgidata.getvalue('owner', '').strip() try: autogen = int(cgidata.getvalue('autogen', '0')) except ValueError: autogen = 0 try: notify = int(cgidata.getvalue('notify', '0')) except ValueError: notify = 0 try: moderate = int( cgidata.getvalue('moderate', mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) except ValueError: moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION password = cgidata.getvalue('password', '').strip() confirm = cgidata.getvalue('confirm', '').strip() auth = cgidata.getvalue('auth', '').strip() langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) if not isinstance(langs, ListType): langs = [langs] # Sanity check safelistname = Utils.websafe(listname) if '@' in listname: request_creation(doc, cgidata, _('List name must not include "@": %(safelistname)s')) return if Utils.list_exists(listname): # BAW: should we tell them the list already exists? This could be # used to mine/guess the existance of non-advertised lists. Then # again, that can be done in other ways already, so oh well. request_creation(doc, cgidata, _('List already exists: %(safelistname)s')) return if not listname: request_creation(doc, cgidata, _('You forgot to enter the list name')) return if not owner: request_creation(doc, cgidata, _('You forgot to specify the list owner')) return if autogen: if password or confirm: request_creation( doc, cgidata, _('''Leave the initial password (and confirmation) fields blank if you want Mailman to autogenerate the list passwords.''')) return password = confirm = Utils.MakeRandomPassword( mm_cfg.ADMIN_PASSWORD_LENGTH) else: if password <> confirm: request_creation(doc, cgidata, _('Initial list passwords do not match')) return if not password: request_creation( doc, cgidata, # The little <!-- ignore --> tag is used so that this string # differs from the one in bin/newlist. The former is destined # for the web while the latter is destined for email, so they # must be different entries in the message catalog. _('The list password cannot be empty<!-- ignore -->')) return # The authorization password must be non-empty, and it must match either # the list creation password or the site admin password ok = 0 if auth: ok = Utils.check_global_password(auth, 0) if not ok: ok = Utils.check_global_password(auth) if not ok: request_creation( doc, cgidata, _('You are not authorized to create new mailing lists')) return # Make sure the web hostname matches one of our virtual domains hostname = Utils.get_domain() if mm_cfg.VIRTUAL_HOST_OVERVIEW and \ not mm_cfg.VIRTUAL_HOSTS.has_key(hostname): safehostname = Utils.websafe(hostname) request_creation(doc, cgidata, _('Unknown virtual host: %(safehostname)s')) return emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST) # We've got all the data we need, so go ahead and try to create the list # See admin.py for why we need to set up the signal handler. mlist = MailList.MailList() def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() # ...and ensure we exit, otherwise race conditions could cause us to # enter MailList.Save() while we're in the unlocked state, and that # could be bad! sys.exit(0) try: # Install the emergency shutdown signal handler signal.signal(signal.SIGTERM, sigterm_handler) pw = sha_new(password).hexdigest() # Guarantee that all newly created files have the proper permission. # proper group ownership should be assured by the autoconf script # enforcing that all directories have the group sticky bit set oldmask = os.umask(002) try: try: mlist.Create(listname, owner, pw, langs, emailhost, urlhost=hostname) finally: os.umask(oldmask) except Errors.EmailAddressError, e: if e.args: s = Utils.websafe(e.args[0]) else: s = Utils.websafe(owner) request_creation(doc, cgidata, _('Bad owner email address: %(s)s')) return except Errors.MMListAlreadyExistsError: # MAS: List already exists so we don't need to websafe it. request_creation(doc, cgidata, _('List already exists: %(listname)s')) return except Errors.BadListNameError, e: if e.args: s = Utils.websafe(e.args[0]) else: s = Utils.websafe(listname) request_creation(doc, cgidata, _('Illegal list name: %(s)s')) return
def main(): global ssort # Figure out which list is being requested parts = Utils.GetPathPieces() if not parts: handle_no_list() 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') handle_no_list(_('No such list <em>%(safelistname)s</em>')) syslog('error', 'admindb: No such list "%s": %s\n', listname, e) return # Now that we know which list to use, set the system's language to it. i18n.set_language(mlist.preferred_language) # Make sure the user is authorized to see this page. cgidata = cgi.FieldStorage(keep_blank_values=1) try: cgidata.getfirst('adminpw', '') 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 # CSRF check safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details'] params = list(cgidata.keys()) if set(params) - set(safe_params): csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token')) else: csrf_checked = True # if password is present, void cookie to force password authentication. if cgidata.getfirst('adminpw'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthListModerator, mm_cfg.AuthSiteAdmin), cgidata.getfirst('adminpw', '')): if 'adminpw' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() remote = os.environ.get('HTTP_FORWARDED_FOR', os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) syslog('security', 'Authorization failed (admindb): list=%s: remote=%s', listname, remote) else: msg = '' Auth.loginpage(mlist, 'admindb', msg=msg) return # Add logout function. Note that admindb may be accessed with # site-wide admin, moderator and list admin privileges. # site admin may have site or admin cookie. (or both?) # See if this is a logout request if len(parts) >= 2 and parts[1] == 'logout': if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site': print(mlist.ZapCookie(mm_cfg.AuthSiteAdmin)) if mlist.AuthContextInfo(mm_cfg.AuthListModerator)[0]: print(mlist.ZapCookie(mm_cfg.AuthListModerator)) print(mlist.ZapCookie(mm_cfg.AuthListAdmin)) Auth.loginpage(mlist, 'admindb', frontpage=1) return # Set up the results document doc = Document() doc.set_language(mlist.preferred_language) # See if we're requesting all the messages for a particular sender, or if # we want a specific held message. sender = None msgid = None details = None envar = os.environ.get('QUERY_STRING') if envar: # POST methods, even if their actions have a query string, don't get # put into FieldStorage's keys :-( qs = cgi.parse_qs(envar).get('sender') if qs and isinstance(qs, list): sender = qs[0] qs = cgi.parse_qs(envar).get('msgid') if qs and isinstance(qs,list): msgid = qs[0] qs = cgi.parse_qs(envar).get('details') if qs and isinstance(qs, list): details = qs[0] # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. # # BAW: Strictly speaking, the list should not need to be locked just to # read the request database. However the request database asserts that # the list is locked in order to load it and it's not worth complicating # that logic. def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() # ...and ensure we exit, otherwise race conditions could cause us to # enter MailList.Save() while we're in the unlocked state, and that # could be bad! sys.exit(0) mlist.Lock() try: # Install the emergency shutdown signal handler signal.signal(signal.SIGTERM, sigterm_handler) realname = mlist.real_name if not list(cgidata.keys()) or 'admlogin' in cgidata: # If this is not a form submission (i.e. there are no keys in the # form) or it's a login, then we don't need to do much special. doc.SetTitle(_('%(realname)s Administrative Database')) elif not details: # This is a form submission doc.SetTitle(_('%(realname)s Administrative Database Results')) if csrf_checked: process_form(mlist, doc, cgidata) else: doc.addError( _('The form lifetime has expired. (request forgery check)')) # Now print the results and we're done. Short circuit for when there # are no pending requests, but be sure to save the results! admindburl = mlist.GetScriptURL('admindb', absolute=1) if not mlist.NumRequestsPending(): title = _('%(realname)s Administrative Database') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.AddItem(_('There are no pending requests.')) doc.AddItem(' ') doc.AddItem(Link(admindburl, _('Click here to reload this page.'))) # Put 'Logout' link before the footer doc.AddItem('\n<div align="right"><font size="+2">') doc.AddItem(Link('%s/logout' % admindburl, '<b>%s</b>' % _('Logout'))) doc.AddItem('</font></div>\n') doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) mlist.Save() return form = Form(admindburl, mlist=mlist, contexts=AUTH_CONTEXTS) # Add the instructions template if details == 'instructions': doc.AddItem(Header( 2, _('Detailed instructions for the administrative database'))) else: doc.AddItem(Header( 2, _('Administrative requests for mailing list:') + ' <em>%s</em>' % mlist.real_name)) if details != 'instructions': form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) nomessages = not mlist.GetHeldMessageIds() if not (details or sender or msgid or nomessages): form.AddItem(Center( '<label>' + CheckBox('discardalldefersp', 0).Format() + ' ' + _('Discard all messages marked <em>Defer</em>') + '</label>' )) # Add a link back to the overview, if we're not viewing the overview! adminurl = mlist.GetScriptURL('admin', absolute=1) d = {'listname' : mlist.real_name, 'detailsurl': admindburl + '?details=instructions', 'summaryurl': admindburl, 'viewallurl': admindburl + '?details=all', 'adminurl' : adminurl, 'filterurl' : adminurl + '/privacy/sender', } addform = 1 if sender: esender = Utils.websafe(sender) d['description'] = _("all of %(esender)s's held messages.") doc.AddItem(Utils.maketext('admindbpreamble.html', d, raw=1, mlist=mlist)) show_sender_requests(mlist, form, sender) elif msgid: d['description'] = _('a single held message.') doc.AddItem(Utils.maketext('admindbpreamble.html', d, raw=1, mlist=mlist)) show_message_requests(mlist, form, msgid) elif details == 'all': d['description'] = _('all held messages.') doc.AddItem(Utils.maketext('admindbpreamble.html', d, raw=1, mlist=mlist)) show_detailed_requests(mlist, form) elif details == 'instructions': doc.AddItem(Utils.maketext('admindbdetails.html', d, raw=1, mlist=mlist)) addform = 0 else: # Show a summary of all requests doc.AddItem(Utils.maketext('admindbsummary.html', d, raw=1, mlist=mlist)) num = show_pending_subs(mlist, form) num += show_pending_unsubs(mlist, form) num += show_helds_overview(mlist, form, ssort) addform = num > 0 # Finish up the document, adding buttons to the form if addform: doc.AddItem(form) form.AddItem('<hr>') if not (details or sender or msgid or nomessages): form.AddItem(Center( '<label>' + CheckBox('discardalldefersp', 0).Format() + ' ' + _('Discard all messages marked <em>Defer</em>') + '</label>' )) form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) # Put 'Logout' link before the footer doc.AddItem('\n<div align="right"><font size="+2">') doc.AddItem(Link('%s/logout' % admindburl, '<b>%s</b>' % _('Logout'))) doc.AddItem('</font></div>\n') doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) # Commit all changes mlist.Save() finally: mlist.Unlock()