def Format(self, indent=0, **kws): charset = 'us-ascii' if self.language and Utils.IsLanguage(self.language): charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s' % charset] output.append('Cache-control: no-cache\n') if not self.suppress_head: kws.setdefault('bgcolor', self.bgcolor) tab = ' ' * indent output.extend([tab, '<HTML>', '<HEAD>']) if mm_cfg.IMAGE_LOGOS: output.append('<LINK REL="SHORTCUT ICON" HREF="%s">' % (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) # Hit all the bases output.append('<META http-equiv="Content-Type" ' 'content="text/html; charset=%s">' % charset) if self.title: output.append('%s<TITLE>%s</TITLE>' % (tab, self.title)) # Add CSS to visually hide some labeling text but allow screen # readers to read it. output.append("""\ <style type="text/css"> div.hidden {position:absolute; left:-10000px; top:auto; width:1px; height:1px; overflow:hidden;} </style> """) if mm_cfg.WEB_HEAD_ADD: output.append(mm_cfg.WEB_HEAD_ADD) output.append('%s</HEAD>' % tab) quals = [] # Default link colors if mm_cfg.WEB_VLINK_COLOR: kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR) if mm_cfg.WEB_ALINK_COLOR: kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR) if mm_cfg.WEB_LINK_COLOR: kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) for k, v in kws.items(): quals.append('%s="%s"' % (k, v)) output.append('%s<BODY %s' % (tab, SPACE.join(quals))) # Language direction direction = Utils.GetDirection(self.language) output.append('dir="%s">' % direction) # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: output.append('%s</BODY>' % tab) output.append('%s</HTML>' % tab) return NL.join(output)
def Format(self, indent=0, **kws): charset = 'us-ascii' if self.language and Utils.IsLanguage(self.language): charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s' % charset] output.append('Cache-control: no-cache\n') if not self.suppress_head: kws.setdefault('bgcolor', self.bgcolor) tab = ' ' * indent output.extend([tab, '<HTML>', '<HEAD>']) if mm_cfg.IMAGE_LOGOS: output.append('<LINK REL="SHORTCUT ICON" HREF="%s">' % (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) # Hit all the bases output.append('<META http-equiv="Content-Type" ' 'content="text/html; charset=%s">' % charset) if self.title: output.append('%s<TITLE>%s</TITLE>' % (tab, self.title)) output.append('%s</HEAD>' % tab) quals = [] # Default link colors if mm_cfg.WEB_VLINK_COLOR: kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR) if mm_cfg.WEB_ALINK_COLOR: kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR) if mm_cfg.WEB_LINK_COLOR: kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) for k, v in kws.items(): quals.append('%s="%s"' % (k, v)) output.append('%s<BODY %s' % (tab, SPACE.join(quals))) # Language direction direction = Utils.GetDirection(self.language) output.append('dir="%s">' % direction) # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: output.append('%s</BODY>' % tab) output.append('%s</HTML>' % tab) return NL.join(output)
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)
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()
def main(): global _ doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) method = Utils.GetRequestMethod() if method.lower() not in ('get', 'post'): title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.addError(_('Invalid request method: %(method)s')) doc.AddItem('<hr>') doc.AddItem(MailmanLogo()) print('Status: 405 Method Not Allowed') print(doc.Format()) return 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 as 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', 'options: No such list "%s": %s\n', listname, e) return # The total contents of the user's response cgidata = cgi.FieldStorage(keep_blank_values=1) # CSRF check safe_params = [ 'displang-button', 'language', 'email', 'password', 'login', 'login-unsub', 'login-remind', 'VARHELP', 'UserOptions' ] try: params = list(cgidata.keys()) 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 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('password'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True # Set the language for the page. If we're coming from the listinfo cgi, # we might have a 'language' key in the cgi data. That was an explicit # preference to view the page in, so we should honor that here. If that's # not available, use the list's default language. language = cgidata.getfirst('language') if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) doc.set_language(language) if lenparts < 2: user = cgidata.getfirst('email') if not user: # If we're coming from the listinfo page and we left the email # address field blank, it's not an error. Likewise if we're # coming from anywhere else. Only issue the error if we came # via one of our buttons. if (cgidata.getfirst('login') or cgidata.getfirst('login-unsub') or cgidata.getfirst('login-remind')): doc.addError(_('No address given')) loginpage(mlist, doc, None, language) print(doc.Format()) return else: user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:]))) # If a user submits a form or URL with post data or query fragments # with multiple occurrences of the same variable, we can get a list # here. Be as careful as possible. if isinstance(user, list) or isinstance(user, tuple): if len(user) == 0: user = '' else: user = user[-1] # Avoid cross-site scripting attacks safeuser = Utils.websafe(user) try: Utils.ValidateEmail(user) except Errors.EmailAddressError: doc.addError(_('Illegal Email Address: %(safeuser)s')) loginpage(mlist, doc, None, language) print(doc.Format()) return # Sanity check the user, but only give the "no such member" error when # using public rosters, otherwise, we'll leak membership information. if not mlist.isMember(user) and mlist.private_roster == 0: doc.addError(_('No such member: %(safeuser)s.')) loginpage(mlist, doc, None, language) print(doc.Format()) return # Find the case preserved email address (the one the user subscribed with) lcuser = user.lower() try: cpuser = mlist.getMemberCPAddress(lcuser) except Errors.NotAMemberError: # This happens if the user isn't a member but we've got private rosters cpuser = None if lcuser == cpuser: cpuser = None # And now we know the user making the request, so set things up to for the # user's stored preferred language, overridden by any form settings for # their new language preference. userlang = cgidata.getfirst('language') if not Utils.IsLanguage(userlang): userlang = mlist.getMemberLanguage(user) doc.set_language(userlang) i18n.set_language(userlang) # Are we processing an unsubscription request from the login screen? msgc = _('If you are a list member, a confirmation email has been sent.') msga = _("""If you are a list member, your unsubscription request has been forwarded to the list administrator for approval.""") if 'login-unsub' in cgidata: # Because they can't supply a password for unsubscribing, we'll need # to do the confirmation dance. if mlist.isMember(user): # We must acquire the list lock in order to pend a request. try: mlist.Lock() # If unsubs require admin approval, then this request has to # be held. Otherwise, send a confirmation. if mlist.unsubscribe_policy: mlist.HoldUnsubscription(user) doc.addError(msga, tag='') else: ip = os.environ.get( 'HTTP_FORWARDED_FOR', os.environ.get( 'HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) mlist.ConfirmUnsubscription(user, userlang, remote=ip) doc.addError(msgc, tag='') mlist.Save() finally: mlist.Unlock() else: # Not a member if mlist.private_roster == 0: # Public rosters doc.addError(_('No such member: %(safeuser)s.')) else: syslog('mischief', 'Unsub attempt of non-member w/ private rosters: %s', user) if mlist.unsubscribe_policy: doc.addError(msga, tag='') else: doc.addError(msgc, tag='') loginpage(mlist, doc, user, language) print(doc.Format()) return # Are we processing a password reminder from the login screen? msg = _("""If you are a list member, your password has been emailed to you.""") if 'login-remind' in cgidata: if mlist.isMember(user): mlist.MailUserPassword(user) doc.addError(msg, tag='') else: # Not a member if mlist.private_roster == 0: # Public rosters doc.addError(_('No such member: %(safeuser)s.')) else: syslog( 'mischief', 'Reminder attempt of non-member w/ private rosters: %s', user) doc.addError(msg, tag='') loginpage(mlist, doc, user, language) print(doc.Format()) return # Get the password from the form. password = cgidata.getfirst('password', '').strip() # Check authentication. We need to know if the credentials match the user # or the site admin, because they are the only ones who are allowed to # change things globally. Specifically, the list admin may not change # values globally. if mm_cfg.ALLOW_SITE_ADMIN_COOKIES: user_or_siteadmin_context = (mm_cfg.AuthUser, mm_cfg.AuthSiteAdmin) else: # Site and list admins are treated equal so that list admin can pass # site admin test. :-( user_or_siteadmin_context = (mm_cfg.AuthUser, ) is_user_or_siteadmin = mlist.WebAuthenticate(user_or_siteadmin_context, password, user) # Authenticate, possibly using the password supplied in the login page if not is_user_or_siteadmin and \ not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), password, user): # Not authenticated, so throw up the login page again. If they tried # to authenticate via cgi (instead of cookie), then print an error # message. if 'password' in cgidata: doc.addError(_('Authentication failed.')) 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', user, listname, remote) # So as not to allow membership leakage, prompt for the email # address and the password here. if mlist.private_roster != 0: syslog('mischief', 'Login failure with private rosters: %s from %s', user, remote) user = None # give an HTTP 401 for authentication failure print('Status: 401 Unauthorized') loginpage(mlist, doc, user, language) print(doc.Format()) return # From here on out, the user is okay to view and modify their membership # options. The first set of checks does not require the list to be # locked. # However, if a form is submitted for a user who has been asynchronously # unsubscribed, uncaught NotAMemberError exceptions can be thrown. if not mlist.isMember(user): loginpage(mlist, doc, user, language) print(doc.Format()) return # Before going further, get the result of CSRF check and do nothing # if it has failed. if csrf_checked == False: doc.addError( _('The form lifetime has expired. (request forgery check)')) options_page(mlist, doc, user, cpuser, userlang) print(doc.Format()) return # See if this is VARHELP on topics. varhelp = None if 'VARHELP' in cgidata: varhelp = cgidata['VARHELP'].value elif os.environ.get('QUERY_STRING'): # POST methods, even if their actions have a query string, don't get # put into FieldStorage's keys :-( qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') if qs and type(qs) == list: varhelp = qs[0] if varhelp: # Sanitize the topic name. varhelp = re.sub('<.*', '', varhelp) topic_details(mlist, doc, user, cpuser, userlang, varhelp) return if 'logout' in cgidata: print(mlist.ZapCookie(mm_cfg.AuthUser, user)) loginpage(mlist, doc, user, language) print(doc.Format()) return if 'emailpw' in cgidata: mlist.MailUserPassword(user) options_page(mlist, doc, user, cpuser, userlang, _('A reminder of your password has been emailed to you.')) print(doc.Format()) return if 'othersubs' in cgidata: # Only the user or site administrator can view all subscriptions. if not is_user_or_siteadmin: doc.addError( _("""The list administrator may not view the other subscriptions for this user."""), _('Note: ')) options_page(mlist, doc, user, cpuser, userlang) print(doc.Format()) return hostname = mlist.host_name title = _('List subscriptions for %(safeuser)s on %(hostname)s') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.AddItem( _('''Click on a link to visit your options page for the requested mailing list.''')) # Troll through all the mailing lists that match host_name and see if # the user is a member. If so, add it to the list. onlists = [] for gmlist in lists_of_member(mlist, user) + [mlist]: extra = '' url = gmlist.GetOptionsURL(user) link = Link(url, gmlist.real_name) if gmlist.getDeliveryStatus(user) != MemberAdaptor.ENABLED: extra += ', ' + _('nomail') if user in gmlist.getDigestMemberKeys(): extra += ', ' + _('digest') link = HTMLFormatObject(link, 0) + extra onlists.append((gmlist.real_name, link)) onlists.sort() items = OrderedList(*[link for name, link in onlists]) doc.AddItem(items) print(doc.Format()) return if 'change-of-address' in cgidata: # We could be changing the user's full name, email address, or both. # Watch out for non-ASCII characters in the member's name. membername = cgidata.getfirst('fullname') # Canonicalize the member's name membername = Utils.canonstr(membername, language) newaddr = cgidata.getfirst('new-address') confirmaddr = cgidata.getfirst('confirm-address') oldname = mlist.getMemberName(user) set_address = set_membername = 0 # See if the user wants to change their email address globally. The # list admin is /not/ allowed to make global changes. globally = cgidata.getfirst('changeaddr-globally') if globally and not is_user_or_siteadmin: doc.addError( _("""The list administrator may not change the names or addresses for this user's other subscriptions. However, the subscription for this mailing list has been changed."""), _('Note: ')) globally = False # We will change the member's name under the following conditions: # - membername has a value # - membername has no value, but they /used/ to have a membername if membername and membername != oldname: # Setting it to a new value set_membername = 1 if not membername and oldname: # Unsetting it set_membername = 1 # We will change the user's address if both newaddr and confirmaddr # are non-blank, have the same value, and aren't the currently # subscribed email address (when compared case-sensitively). If both # are blank, but membername is set, we ignore it, otherwise we print # an error. msg = '' if newaddr and confirmaddr: if newaddr != confirmaddr: options_page(mlist, doc, user, cpuser, userlang, _('Addresses did not match!')) print(doc.Format()) return if newaddr == cpuser: options_page(mlist, doc, user, cpuser, userlang, _('You are already using that email address')) print(doc.Format()) return # If they're requesting to subscribe an address which is already a # member, and they're /not/ doing it globally, then refuse. # Otherwise, we'll agree to do it globally (with a warning # message) and let ApprovedChangeMemberAddress() handle already a # member issues. if mlist.isMember(newaddr): safenewaddr = Utils.websafe(newaddr) if globally: listname = mlist.real_name msg += _("""\ The new address you requested %(newaddr)s is already a member of the %(listname)s mailing list, however you have also requested a global change of address. Upon confirmation, any other mailing list containing the address %(safeuser)s will be changed. """) # Don't return else: options_page( mlist, doc, user, cpuser, userlang, _('The new address is already a member: %(newaddr)s')) print(doc.Format()) return set_address = 1 elif (newaddr or confirmaddr) and not set_membername: options_page(mlist, doc, user, cpuser, userlang, _('Addresses may not be blank')) print(doc.Format()) return # Standard sigterm handler. def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() sys.exit(0) signal.signal(signal.SIGTERM, sigterm_handler) if set_address: if cpuser is None: cpuser = user # Register the pending change after the list is locked msg += _('A confirmation message has been sent to %(newaddr)s. ') mlist.Lock() try: try: mlist.ChangeMemberAddress(cpuser, newaddr, globally) mlist.Save() finally: mlist.Unlock() except Errors.MMBadEmailError: msg = _('Bad email address provided') except Errors.MMHostileAddress: msg = _('Illegal email address provided') except Errors.MMAlreadyAMember: msg = _('%(newaddr)s is already a member of the list.') except Errors.MembershipIsBanned: owneraddr = mlist.GetOwnerEmail() msg = _("""%(newaddr)s is banned from this list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.""") if set_membername: mlist.Lock() try: mlist.ChangeMemberName(user, membername, globally) mlist.Save() finally: mlist.Unlock() msg += _('Member name successfully changed. ') options_page(mlist, doc, user, cpuser, userlang, msg) print(doc.Format()) return if 'changepw' in cgidata: # Is this list admin and is list admin allowed to change passwords. if not (is_user_or_siteadmin or mm_cfg.OWNERS_CAN_CHANGE_MEMBER_PASSWORDS): doc.addError( _("""The list administrator may not change the password for a user.""")) options_page(mlist, doc, user, cpuser, userlang) print(doc.Format()) return newpw = cgidata.getfirst('newpw', '').strip() confirmpw = cgidata.getfirst('confpw', '').strip() if not newpw or not confirmpw: options_page(mlist, doc, user, cpuser, userlang, _('Passwords may not be blank')) print(doc.Format()) return if newpw != confirmpw: options_page(mlist, doc, user, cpuser, userlang, _('Passwords did not match!')) print(doc.Format()) return # See if the user wants to change their passwords globally, however # the list admin is /not/ allowed to change passwords globally. pw_globally = cgidata.getfirst('pw-globally') if pw_globally and not is_user_or_siteadmin: doc.addError( _("""The list administrator may not change the password for this user's other subscriptions. However, the password for this mailing list has been changed."""), _('Note: ')) pw_globally = False mlists = [mlist] if pw_globally: mlists.extend(lists_of_member(mlist, user)) for gmlist in mlists: change_password(gmlist, user, newpw, confirmpw) # Regenerate the cookie so a re-authorization isn't necessary print(mlist.MakeCookie(mm_cfg.AuthUser, user)) options_page(mlist, doc, user, cpuser, userlang, _('Password successfully changed.')) print(doc.Format()) return if 'unsub' in cgidata: # Was the confirming check box turned on? if not cgidata.getfirst('unsubconfirm'): options_page( mlist, doc, user, cpuser, userlang, _('''You must confirm your unsubscription request by turning on the checkbox below the <em>Unsubscribe</em> button. You have not been unsubscribed!''')) print(doc.Format()) return # Standard signal handler def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() sys.exit(0) # Okay, zap them. Leave them sitting at the list's listinfo page. We # must own the list lock, and we want to make sure the user (BAW: and # list admin?) is informed of the removal. signal.signal(signal.SIGTERM, sigterm_handler) mlist.Lock() needapproval = False try: _ = D_ try: mlist.DeleteMember(user, _('via the member options page'), userack=1) except Errors.MMNeedApproval: needapproval = True except Errors.NotAMemberError: # MAS This except should really be in the outer try so we # don't save the list redundantly, but except and finally in # the same try requires Python >= 2.5. # Setting a switch and making the Save() conditional doesn't # seem worth it as the Save() won't change anything. pass mlist.Save() finally: _ = i18n._ mlist.Unlock() # Now throw up some results page, with appropriate links. We can't # drop them back into their options page, because that's gone now! fqdn_listname = mlist.GetListEmail() owneraddr = mlist.GetOwnerEmail() url = mlist.GetScriptURL('listinfo', absolute=1) title = _('Unsubscription results') doc.SetTitle(title) doc.AddItem(Header(2, title)) if needapproval: doc.AddItem( _("""Your unsubscription request has been received and forwarded on to the list moderators for approval. You will receive notification once the list moderators have made their decision.""")) else: doc.AddItem( _("""You have been successfully unsubscribed from the mailing list %(fqdn_listname)s. If you were receiving digest deliveries you may get one more digest. If you have any questions about your unsubscription, please contact the list owners at %(owneraddr)s.""")) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return if 'options-submit' in cgidata: # Digest action flags digestwarn = 0 cantdigest = 0 mustdigest = 0 newvals = [] # First figure out which options have changed. The item names come # from FormatOptionButton() in HTMLFormatter.py for item, flag in ( ('digest', mm_cfg.Digests), ('mime', mm_cfg.DisableMime), ('dontreceive', mm_cfg.DontReceiveOwnPosts), ('ackposts', mm_cfg.AcknowledgePosts), ('disablemail', mm_cfg.DisableDelivery), ('conceal', mm_cfg.ConcealSubscription), ('remind', mm_cfg.SuppressPasswordReminder), ('rcvtopic', mm_cfg.ReceiveNonmatchingTopics), ('nodupes', mm_cfg.DontReceiveDuplicates), ): try: newval = int(cgidata.getfirst(item)) except (TypeError, ValueError): newval = None # Skip this option if there was a problem or it wasn't changed. # Note that delivery status is handled separate from the options # flags. if newval is None: continue elif flag == mm_cfg.DisableDelivery: status = mlist.getDeliveryStatus(user) # Here, newval == 0 means enable, newval == 1 means disable if not newval and status != MemberAdaptor.ENABLED: newval = MemberAdaptor.ENABLED elif newval and status == MemberAdaptor.ENABLED: newval = MemberAdaptor.BYUSER else: continue elif newval == mlist.getMemberOption(user, flag): continue # Should we warn about one more digest? if flag == mm_cfg.Digests and \ newval == 0 and mlist.getMemberOption(user, flag): digestwarn = 1 newvals.append((flag, newval)) # The user language is handled a little differently if userlang not in mlist.GetAvailableLanguages(): newvals.append((SETLANGUAGE, mlist.preferred_language)) else: newvals.append((SETLANGUAGE, userlang)) # Process user selected topics, but don't make the changes to the # MailList object; we must do that down below when the list is # locked. topicnames = cgidata.getvalue('usertopic') if topicnames: # Some topics were selected. topicnames can actually be a string # or a list of strings depending on whether more than one topic # was selected or not. if not isinstance(topicnames, list): # Assume it was a bare string, so listify it topicnames = [topicnames] # unquote the topic names topicnames = [urllib.parse.unquote_plus(n) for n in topicnames] # The standard sigterm handler (see above) def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() sys.exit(0) # Now, lock the list and perform the changes mlist.Lock() try: signal.signal(signal.SIGTERM, sigterm_handler) # `values' is a tuple of flags and the web values for flag, newval in newvals: # Handle language settings differently if flag == SETLANGUAGE: mlist.setMemberLanguage(user, newval) # Handle delivery status separately elif flag == mm_cfg.DisableDelivery: mlist.setDeliveryStatus(user, newval) else: try: mlist.setMemberOption(user, flag, newval) except Errors.CantDigestError: cantdigest = 1 except Errors.MustDigestError: mustdigest = 1 # Set the topics information. mlist.setMemberTopics(user, topicnames) mlist.Save() finally: mlist.Unlock() # A bag of attributes for the global options class Global: enable = None remind = None nodupes = None mime = None def __bool__(self): return len(list(self.__dict__.keys())) > 0 globalopts = Global() # The enable/disable option and the password remind option may have # their global flags sets. if cgidata.getfirst('deliver-globally'): # Yes, this is inefficient, but the list is so small it shouldn't # make much of a difference. for flag, newval in newvals: if flag == mm_cfg.DisableDelivery: globalopts.enable = newval break if cgidata.getfirst('remind-globally'): for flag, newval in newvals: if flag == mm_cfg.SuppressPasswordReminder: globalopts.remind = newval break if cgidata.getfirst('nodupes-globally'): for flag, newval in newvals: if flag == mm_cfg.DontReceiveDuplicates: globalopts.nodupes = newval break if cgidata.getfirst('mime-globally'): for flag, newval in newvals: if flag == mm_cfg.DisableMime: globalopts.mime = newval break # Change options globally, but only if this is the user or site admin, # /not/ if this is the list admin. if globalopts: if not is_user_or_siteadmin: doc.addError( _("""The list administrator may not change the options for this user's other subscriptions. However the options for this mailing list subscription has been changed."""), _('Note: ')) else: for gmlist in lists_of_member(mlist, user): global_options(gmlist, user, globalopts) # Now print the results if cantdigest: msg = _('''The list administrator has disabled digest delivery for this list, so your delivery option has not been set. However your other options have been set successfully.''') elif mustdigest: msg = _('''The list administrator has disabled non-digest delivery for this list, so your delivery option has not been set. However your other options have been set successfully.''') else: msg = _('You have successfully set your options.') if digestwarn: msg += _('You may get one last digest.') options_page(mlist, doc, user, cpuser, userlang, msg) print(doc.Format()) return if mlist.isMember(user): options_page(mlist, doc, user, cpuser, userlang) else: loginpage(mlist, doc, user, userlang) print(doc.Format())
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 # The total contents of the user's response cgidata = cgi.FieldStorage(keep_blank_values=1) # Set the language for the page. If we're coming from the listinfo cgi, # we might have a 'language' key in the cgi data. That was an explicit # preference to view the page in, so we should honor that here. If that's # not available, use the list's default language. language = cgidata.getvalue('language') if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) doc.set_language(language) if lenparts < 2: user = cgidata.getvalue('email') if not user: # If we're coming from the listinfo page and we left the email # address field blank, it's not an error. Likewise if we're # coming from anywhere else. Only issue the error if we came # via one of our buttons. if (cgidata.getvalue('login') or cgidata.getvalue('login-unsub') or cgidata.getvalue('login-remind')): doc.addError(_('No address given')) loginpage(mlist, doc, None, language)
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 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())