Пример #1
0
    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)
Пример #2
0
 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)
Пример #4
0
        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()
Пример #5
0
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())
Пример #6
0
        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()
Пример #8
0
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())