Example #1
1
def gen_dot():
    d = Dot()
    nodes = dict()
    login = read_ssv_file("vpopmail.login")
    db = MySQLdb.connect(host="localhost", user=login[0], passwd=login[2], db=login[1])
    c = db.cursor()
    c.execute("SELECT alias, valias_line FROM valias WHERE domain=%s", (MAILDOMAIN,))
    for alias, target in c.fetchall():
        assert target[0] == "&"
        target = target[1:]
        alias += "@" + MAILDOMAIN
        if not alias in nodes:
            nodes[alias] = Node(alias)
            d.add_node(nodes[alias])
        if not target in nodes:
            nodes[target] = Node(target)
            d.add_node(nodes[target])
        d.add_edge(Edge(nodes[alias], nodes[target]))
    for list in Utils.list_names():
        if list == "plukdenacht2008":
            continue
        source = list + "@" + LISTDOMAIN
        if not source in nodes:
            nodes[source] = Node(source)
            d.add_node(nodes[source])
        m = MailList.MailList(list, lock=False)
        for member in m.members:
            if not member in nodes:
                nodes[member] = Node(member)
                d.add_node(nodes[member])
            d.add_edge(Edge(nodes[source], nodes[member]))
    d.write("the.dot")
Example #2
0
def process(mlist, msg, msgdata):
    # Extract the sender's address and find them in the user database
    sender = msgdata.get('original_sender', msg.get_sender())
    try:
        ack = mlist.getMemberOption(sender, mm_cfg.AcknowledgePosts)
        if not ack:
            return
    except Errors.NotAMemberError:
        return
    # Okay, they want acknowledgement of their post.  Give them their original
    # subject.  BAW: do we want to use the decoded header?
    origsubj = msgdata.get('origsubj', msg.get('subject', _('(no subject)')))
    # Get the user's preferred language
    lang = msgdata.get('lang', mlist.getMemberLanguage(sender))
    # Now get the acknowledgement template
    realname = mlist.real_name
    text = Utils.maketext(
        'postack.txt',
        {'subject'     : Utils.oneline(origsubj, Utils.GetCharSet(lang)),
         'listname'    : realname,
         'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
         'optionsurl'  : mlist.GetOptionsURL(sender, absolute=1),
         }, lang=lang, mlist=mlist, raw=1)
    # Craft the outgoing message, with all headers and attributes
    # necessary for general delivery.  Then enqueue it to the outgoing
    # queue.
    subject = _('%(realname)s post acknowledgement')
    usermsg = Message.UserNotification(sender, mlist.GetBouncesEmail(),
                                       subject, text, lang)
    usermsg.send(mlist)
    def do_command(self, cmd, args=None):
        if args is None:
            args = ()
        # Try to import a command handler module for this command
        modname = 'Mailman.Commands.cmd_' + cmd
        try:
            __import__(modname)
            handler = sys.modules[modname]
        # ValueError can be raised if cmd has dots in it.
        except (ImportError, ValueError):
            # If we're on line zero, it was the Subject: header that didn't
            # contain a command.  It's possible there's a Re: prefix (or
            # localized version thereof) on the Subject: line that's messing
            # things up.  Pop the prefix off and try again... once.
            #
            # If that still didn't work it isn't enough to stop processing.
            # BAW: should we include a message that the Subject: was ignored?
            if not self.subjcmdretried and args:
                self.subjcmdretried += 1
                cmd = args.pop(0)
                return self.do_command(cmd, args)
            return self.lineno <> 0
	# with Dlists, we don't allow email subscription
  	if DlistUtils.enabled(self.mlist) and (cmd == 'subscribe' or cmd == 'join'):
            realname = self.mlist.real_name
            domain = Utils.get_domain()
            self.results.append(Utils.wrap(_("""\
This list cannot be subscribed to via email. 
Please use the website at http://%(domain)s/mailman/listinfo/%(realname)s .
""")))
            return self.lineno <> 0 # superstitious behavior as they do it above

	return handler.process(self, args)
Example #4
0
 def __sendAdminBounceNotice(self, member, msg):
     # BAW: This is a bit kludgey, but we're not providing as much
     # information in the new admin bounce notices as we used to (some of
     # it was of dubious value).  However, we'll provide empty, strange, or
     # meaningless strings for the unused %()s fields so that the language
     # translators don't have to provide new templates.
     siteowner = Utils.get_site_email(self.host_name)
     text = Utils.maketext(
         "bounce.txt",
         {
             "listname": self.real_name,
             "addr": member,
             "negative": "",
             "did": _("disabled"),
             "but": "",
             "reenable": "",
             "owneraddr": siteowner,
         },
         mlist=self,
     )
     subject = _("Bounce action notification")
     umsg = Message.UserNotification(self.GetOwnerEmail(), siteowner, subject, lang=self.preferred_language)
     # BAW: Be sure you set the type before trying to attach, or you'll get
     # a MultipartConversionError.
     umsg.set_type("multipart/mixed")
     umsg.attach(MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language)))
     if isinstance(msg, StringType):
         umsg.attach(MIMEText(msg))
     else:
         umsg.attach(MIMEMessage(msg))
     umsg.send(self)
    def send_response(self):
        # Helper
        def indent(lines):
            return ['    ' + line for line in lines]
        # Quick exit for some commands which don't need a response
        if not self.respond:
            return
        resp = [Utils.wrap(_("""\
The results of your email command are provided below.
Attached is your original message.
"""))]
        if self.results:
            resp.append(_('- Results:'))
            resp.extend(indent(self.results))
        # Ignore empty lines
        unprocessed = [line for line in self.commands[self.lineno:]
                       if line and line.strip()]
        if unprocessed:
            resp.append(_('\n- Unprocessed:'))
            resp.extend(indent(unprocessed))
        if not unprocessed and not self.results:
            # The user sent an empty message; return a helpful one.
            resp.append(Utils.wrap(_("""\
No commands were found in this message.
To obtain instructions, send a message containing just the word "help".
""")))
        if self.ignored:
            resp.append(_('\n- Ignored:'))
            resp.extend(indent(self.ignored))
        resp.append(_('\n- Done.\n\n'))
        # Encode any unicode strings into the list charset, so we don't try to
        # join unicode strings and invalid ASCII.
        charset = Utils.GetCharSet(self.msgdata['lang'])
        encoded_resp = []
        for item in resp:
            if isinstance(item, UnicodeType):
                item = item.encode(charset, 'replace')
            encoded_resp.append(item)
        results = MIMEText(NL.join(encoded_resp), _charset=charset)
        # Safety valve for mail loops with misconfigured email 'bots.  We
        # don't respond to commands sent with "Precedence: bulk|junk|list"
        # unless they explicitly "X-Ack: yes", but not all mail 'bots are
        # correctly configured, so we max out the number of responses we'll
        # give to an address in a single day.
        #
        # BAW: We wait until now to make this decision since our sender may
        # not be self.msg.get_sender(), but I'm not sure this is right.
        recip = self.returnaddr or self.msg.get_sender()
        if not self.mlist.autorespondToSender(recip, self.msgdata['lang']):
            return
        msg = Message.UserNotification(
            recip,
            self.mlist.GetBouncesEmail(),
            _('The results of your email commands'),
            lang=self.msgdata['lang'])
        msg.set_type('multipart/mixed')
        msg.attach(results)
        orig = MIMEMessage(self.msg)
        msg.attach(orig)
        msg.send(self.mlist)
Example #6
0
def loginpage(mlist, scriptname, msg='', frontpage=None):
    url = mlist.GetScriptURL(scriptname)
    if frontpage:
        actionurl = url
    else:
        actionurl = Utils.GetRequestURI(url)
    if msg:
        msg = FontAttr(msg, color='#ff0000', size='+1').Format()
        # give an HTTP 401 for authentication failure
        print 'Status: 401 Unauthorized'
    if scriptname == 'admindb':
        who = _('Moderator')
    else:
        who = _('Administrator')
    # Language stuff
    charset = Utils.GetCharSet(mlist.preferred_language)
    print 'Content-type: text/html; charset=' + charset + '\n\n'
    print Utils.maketext(
        'admlogin.html',
        {'listname': mlist.real_name,
         'path'    : actionurl,
         'message' : msg,
         'who'     : who,
         }, mlist=mlist)
    print mlist.GetMailmanFooter()
Example #7
0
    def _cleanup(self):
        """Clean up upon exit from the main processing loop.

        Called when the Runner's main loop is stopped, this should perform
        any necessary resource deallocation.  Its return value is irrelevant.
        """
        Utils.reap(self._kids)
Example #8
0
    def errcheck (self, action):
        """Performs all error checks. Returns None is all's good. Otherwise
        returns a string with error message."""

        if not can_create_lists(self.curr_user):
            return 'You are not authorized to creates lists on this server'

        if len(self.owner) <= 0:
            return 'Cannot create list without a owner.'

        if self.ln == '':
            return 'You forgot to enter the list name'

        if '@' in self.ln:
            return 'List name must not include "@": %s' % self.safeln

        if action == 'create' and Utils.list_exists(self.ln):
            return 'List already exists: %s' % self.safe_ln

        if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
          not mm_cfg.VIRTUAL_HOSTS.has_key(self.hn):
            safehostname = Utils.websafe(self.hn)
            return 'Unknown virtual host: %s' % safehostname

        return None
Example #9
0
def topic_details(mlist, doc, user, cpuser, userlang, varhelp):
    # Find out which topic the user wants to get details of
    reflist = varhelp.split("/")
    name = None
    topicname = _("<missing>")
    if len(reflist) == 1:
        topicname = urllib.unquote_plus(reflist[0])
        for name, pattern, description, emptyflag in mlist.topics:
            if name == topicname:
                break
        else:
            name = None

    if not name:
        options_page(mlist, doc, user, cpuser, userlang, _("Requested topic is not valid: %(topicname)s"))
        print doc.Format()
        return

    table = Table(border=3, width="100%")
    table.AddRow([Center(Bold(_("Topic filter details")))])
    table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, bgcolor=mm_cfg.WEB_SUBHEADER_COLOR)
    table.AddRow([Bold(Label(_("Name:"))), Utils.websafe(name)])
    table.AddRow([Bold(Label(_("Pattern (as regexp):"))), "<pre>" + Utils.websafe(pattern) + "</pre>"])
    table.AddRow([Bold(Label(_("Description:"))), Utils.websafe(description)])
    # Make colors look nice
    for row in range(1, 4):
        table.AddCellInfo(row, 0, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR)

    options_page(mlist, doc, user, cpuser, userlang, table.Format())
    print doc.Format()
Example #10
0
    def scrub_msg822(self, part):
        # submessage
        submsg = part.get_payload(0)
        omask = os.umask(002)
        try:
            url = save_attachment(self.mlist, part, self.dir)
        finally:
            os.umask(omask)
        subject = submsg.get('subject', _('no subject'))
        subject = Utils.oneline(subject, self.lcset)
        date = submsg.get('date', _('no date'))
        who = submsg.get('from', _('unknown sender'))
        who = Utils.oneline(who, self.lcset)
        size = len(str(submsg))
        self.msgtexts.append(unicode(_("""\
An embedded message was scrubbed...
From: %(who)s
Subject: %(subject)s
Date: %(date)s
Size: %(size)s
URL: %(url)s
"""), self.lcset))
        # Replace this part because subparts should not be walk()-ed.
        del part['content-type']
        part.set_payload('blah blah', 'us-ascii')
Example #11
0
    def create(self, email):
        if self.exists:
            raise ListAlreadyExists

        langs = [mm_cfg.DEFAULT_SERVER_LANGUAGE]
        pw = Utils.MakeRandomPassword()
        pw_hashed = Utils.sha_new(pw).hexdigest()
        urlhost = mm_cfg.DEFAULT_URL_HOST
        host_name = mm_cfg.DEFAULT_EMAIL_HOST
        web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost

        # TODO: Add some atomicity. We should roll back changes using
        #       a try/else if something (like MTA alias update) fails
        #       before the function terminates.

        try:
            oldmask = os.umask(002)
            self.mlist.Create(self.name, email, pw_hashed, langs=langs,
                              emailhost=host_name, urlhost=urlhost)
            self.mlist.preferred_language = langs[0]

            # Reply-To set to list address
            self.mlist.reply_goes_to_list = 2
            self.mlist.reply_to_address = "%s@%s" % (self.list, self.domain)

            # Allow messages from listname@domain
            self.mlist.acceptable_aliases = "%s@%s\n" % (self.list, self.domain)

            self.mlist.subject_prefix = "[%s] " % (self.list)
            self.mlist.msg_footer = ""
            self.mlist.subscribe_policy = 2 # Confirm and approve
            self.mlist.max_message_size = 20480 # 20M

            self.mlist.Save()

        finally:
            os.umask(oldmask)
            self.mlist.Unlock()

        if mm_cfg.MTA:
            modname = 'Mailman.MTA.' + mm_cfg.MTA
            __import__(modname)
            sys.modules[modname].create(self.mlist)

        siteowner = Utils.get_site_email(self.mlist.host_name, 'owner')
        text = Utils.maketext(
            'newlist.txt',
            {'listname'    : self.name,
             'password'    : pw,
             'admin_url'   : self.mlist.GetScriptURL('admin', absolute=1),
             'listinfo_url': self.mlist.GetScriptURL('listinfo', absolute=1),
             'requestaddr' : self.mlist.GetRequestEmail(),
             'siteowner'   : siteowner,
             }, mlist=self.mlist)


        msg = Message.UserNotification(email, siteowner, 'Your new mailing list: %s' % self.name,
                                        text, self.mlist.preferred_language)
        msg.send(self.mlist)
Example #12
0
def do_reject(mlist):
    listowner = mlist.GetOwnerEmail()
    if mlist.nonmember_rejection_notice:
        raise Errors.RejectMessage, \
              Utils.wrap(_(mlist.nonmember_rejection_notice))
    else:
        raise Errors.RejectMessage, Utils.wrap(_("""\
You are not allowed to post to this mailing list, and your message has been
automatically rejected.  If you think that your messages are being rejected in
error, contact the mailing list owner at %(listowner)s."""))
Example #13
0
def do_reject(mlist):
    listowner = mlist.GetOwnerEmail()
    if mlist.nonmember_rejection_notice:
        raise Errors.RejectMessage, \
              Utils.wrap(_(mlist.nonmember_rejection_notice))
    else:
        raise Errors.RejectMessage, Utils.wrap(_("""\
Your message has been rejected, probably because you are not subscribed to the
mailing list and the list's policy is to prohibit non-members from posting to
it.  If you think that your messages are being rejected in error, contact the
mailing list owner at %(listowner)s."""))
def openidreg_overview(lang, msg=''):
    # Present the general listinfo overview
    hostname = Utils.get_domain()
    # Set up the document and assign it the correct language.  The only one we
    # know about at the moment is the server's default.
    doc = Document()
#    doc.set_language(lang)
    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)

    legend = _(" OpenID Registeration for Systers Mailing Lists")
    doc.SetTitle(legend)

    table = Table(border=0, width="100%")
    table.AddRow([Center(Header(2, legend))])
    table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2,
                      bgcolor=mm_cfg.WEB_HEADER_COLOR)

    # Skip any mailing lists that isn't advertised.

    if msg:
        greeting = FontAttr(msg, color="ff5060", size="+1")
    else:
        greeting = FontAttr(_('Welcome!'), size='+2')

    welcome = [greeting]
    mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format()
    

    # set up some local variables
    adj = msg and _('right') or ''
    siteowner = Utils.get_site_email()
    welcome.extend(
        (_(''' This is the Systers OpenID registeration form . To enable your systers account fill in the following entries.
        <p>or Go back to the listinfo page if already using it '''),
         Link(Utils.ScriptURL('listinfo'),
              _('the mailing lists overview page')),
         _(''' <p>If you are having trouble using the lists, please contact '''),
         Link('mailto:' + siteowner, siteowner),
         '.<p>',
         FormatOpenIDLogin(),
         '<p>'))
    
         
    table.AddRow([apply(Container, welcome)])
    table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2)

    

    doc.AddItem(table)
    doc.AddItem('<hr>')
    doc.AddItem(MailmanLogo())
    print doc.Format()
Example #15
0
def show_pending_unsubs(mlist, form):
    # Add the pending unsubscription request section
    lang = mlist.preferred_language
    pendingunsubs = mlist.GetUnsubscriptionIds()
    if not pendingunsubs:
        return 0
    table = Table(border=2)
    table.AddRow([Center(Bold(_('User address/name'))),
                  Center(Bold(_('Your decision'))),
                  Center(Bold(_('Reason for refusal')))
                  ])
    # Alphabetical order by email address
    byaddrs = {}
    for id in pendingunsubs:
        addr = mlist.GetRecord(id)
        byaddrs.setdefault(addr, []).append(id)
    addrs = byaddrs.keys()
    addrs.sort()
    num = 0
    for addr, ids in byaddrs.items():
        # Eliminate duplicates
        for id in ids[1:]:
            mlist.HandleRequest(id, mm_cfg.DISCARD)
        id = ids[0]
        addr = mlist.GetRecord(id)
        try:
            fullname = Utils.uncanonstr(mlist.getMemberName(addr), lang)
        except Errors.NotAMemberError:
            # They must have been unsubscribed elsewhere, so we can just
            # discard this record.
            mlist.HandleRequest(id, mm_cfg.DISCARD)
            continue
        num += 1
        # While the address may be a unicode, it must be ascii
        paddr = addr.encode('us-ascii', 'replace')
        table.AddRow(['%s<br><em>%s</em>' % (paddr, Utils.websafe(fullname)),
                      RadioButtonArray(id, (_('Defer'),
                                            _('Approve'),
                                            _('Reject'),
                                            _('Discard')),
                                       values=(mm_cfg.DEFER,
                                               mm_cfg.UNSUBSCRIBE,
                                               mm_cfg.REJECT,
                                               mm_cfg.DISCARD),
                                       checked=0),
                      TextBox('comment-%d' % id, size=45)
                      ])
    if num > 0:
        form.AddItem('<hr>')
        form.AddItem(Center(Header(2, _('Unsubscription Requests'))))
        form.AddItem(table)
    return num
Example #16
0
 def __init__(self, whichq, slice=None, numslices=1, recover=False):
     self._whichq = whichq
     # Create the directory if it doesn't yet exist.
     Utils.makedirs(self._whichq, 0770)
     # Fast track for no slices
     self._lower = None
     self._upper = None
     # BAW: test performance and end-cases of this algorithm
     if numslices <> 1:
         self._lower = ((shamax + 1) * slice) / numslices
         self._upper = (((shamax + 1) * (slice + 1)) / numslices) - 1
     if recover:
         self.recover_backup_files()
Example #17
0
    def SendSubscribeAck(self, name, password, digest, text=""):
        pluser = self.getMemberLanguage(name)
        # Need to set this here to get the proper l10n of the Subject:
        i18n.set_language(pluser)
        if self.welcome_msg:
            welcome = Utils.wrap(self.welcome_msg) + "\n"
        else:
            welcome = ""
        if self.umbrella_list:
            addr = self.GetMemberAdminEmail(name)
            umbrella = Utils.wrap(
                _(
                    """\
Note: Since this is a list of mailing lists, administrative
notices like the password reminder will be sent to
your membership administrative address, %(addr)s."""
                )
            )
        else:
            umbrella = ""
        # get the text from the template
        text += Utils.maketext(
            "subscribeack.txt",
            {
                "real_name": self.real_name,
                "host_name": self.host_name,
                "welcome": welcome,
                "umbrella": umbrella,
                "emailaddr": self.GetListEmail(),
                "listinfo_url": self.GetScriptURL("listinfo", absolute=True),
                "optionsurl": self.GetOptionsURL(name, absolute=True),
                "password": password,
                "user": self.getMemberCPAddress(name),
            },
            lang=pluser,
            mlist=self,
        )
        if digest:
            digmode = _(" (Digest mode)")
        else:
            digmode = ""
        realname = self.real_name
        msg = Message.UserNotification(
            self.GetMemberAdminEmail(name),
            self.GetRequestEmail(),
            _('Welcome to the "%(realname)s" mailing list%(digmode)s'),
            text,
            pluser,
        )
        msg["X-No-Archive"] = "yes"
        msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES)
Example #18
0
def show_pending_subs(mlist, form):
    # Add the subscription request section
    pendingsubs = mlist.GetSubscriptionIds()
    if not pendingsubs:
        return 0
    form.AddItem('<hr>')
    form.AddItem(Center(Header(2, _('Subscription Requests'))))
    table = Table(border=2)
    table.AddRow([Center(Bold(_('Address/name'))),
                  Center(Bold(_('Your decision'))),
                  Center(Bold(_('Reason for refusal')))
                  ])
    # Alphabetical order by email address
    byaddrs = {}
    for id in pendingsubs:
        addr = mlist.GetRecord(id)[1]
        byaddrs.setdefault(addr, []).append(id)
    addrs = byaddrs.items()
    addrs.sort()
    num = 0
    for addr, ids in addrs:
        # Eliminate duplicates
        for id in ids[1:]:
            mlist.HandleRequest(id, mm_cfg.DISCARD)
        id = ids[0]
        time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id)
        fullname = Utils.uncanonstr(fullname, mlist.preferred_language)
        radio = RadioButtonArray(id, (_('Defer'),
                                      _('Approve'),
                                      _('Reject'),
                                      _('Discard')),
                                 values=(mm_cfg.DEFER,
                                         mm_cfg.SUBSCRIBE,
                                         mm_cfg.REJECT,
                                         mm_cfg.DISCARD),
                                 checked=0).Format()
        if addr not in mlist.ban_list:
            radio += ('<br>' + '<label>' +
                     CheckBox('ban-%d' % id, 1).Format() +
                     '&nbsp;' + _('Permanently ban from this list') +
                     '</label>')
        # While the address may be a unicode, it must be ascii
        paddr = addr.encode('us-ascii', 'replace')
        table.AddRow(['%s<br><em>%s</em>' % (paddr, Utils.websafe(fullname)),
                      radio,
                      TextBox('comment-%d' % id, size=40)
                      ])
        num += 1
    if num > 0:
        form.AddItem(table)
    return num
Example #19
0
 def __init__(self, text=''):
     from Mailman.pythonlib.StringIO import StringIO
     # NOTE: text if supplied must begin with valid rfc822 headers.  It can
     # also begin with the body of the message but in that case you better
     # make sure that the first line does NOT contain a colon!
     Message.__init__(self, StringIO(text))
     # RFC 2822 requires a Date: header, and while most MTAs add one if
     # it's missing, qmail does not.
     if not self.get('date'):
         self['Date'] = Utils.formatdate(localtime=1)
     # RFC 2822 recommends a Message-ID: header, and while most MTAs add
     # one if it's missing, qmail does not.
     if not self.get('message-id'):
         self['Message-ID'] = Utils.make_msgid(idstring='Mailman')
def lists_of_member(safeuser):
    hostname = mm_cfg.DEFAULT_URL_HOST
    onlists = []
    for listname in Utils.list_names():
        # The current list will always handle things in the mainline
        if listname == Utils.list_names():
            continue
        glist = MailList.MailList(listname, lock=0)
        if glist.host_name <> hostname:
            continue
        if not glist.isMember(safeuser):
            continue
        onlists.append(glist)
    return onlists
Example #21
0
 def finish(self, filebase, preserve=False):
     bakfile = os.path.join(self._whichq, filebase + '.bak')
     try:
         if preserve:
             psvfile = os.path.join(config.SHUNTQUEUE_DIR,
                                    filebase + '.psv')
             # Create the directory if it doesn't yet exist.
             Utils.makedirs(config.SHUNTQUEUE_DIR, 0770)
             os.rename(bakfile, psvfile)
         else:
             os.unlink(bakfile)
     except EnvironmentError, e:
         elog.exception('Failed to unlink/preserve backup file: %s',
                        bakfile)
Example #22
0
def quote(s, is_header=False):
    if is_header:
        h = Utils.oneline(s, 'utf-8')
    else:
        h = s

    # Remove illegal XML characters
    # Try to decode UTF-8, so that Utils.uquote can escape multibyte characters
    # correctly.
    try:
        hclean = h.decode('utf-8')
        hclean = u''.join(re.split(u'[\x00-\x08\x0B-\x1f]+', hclean))
    except UnicodeDecodeError:
        hclean = ''.join(re.split('[\x00-\x08\x0B-\x1f]+', h))
    return Utils.uquote(hclean.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;'))
Example #23
0
def process(res, args):
    mlist = res.mlist
    if args:
        res.results.append(_('Usage:'))
        res.results.append(gethelp(mlist))
        return STOP
    hostname = mlist.host_name
    res.results.append(_('Public mailing lists at %(hostname)s:'))
    lists = Utils.list_names()
    lists.sort()
    i = 1
    for listname in lists:
        if listname == mlist.internal_name():
            xlist = mlist
        else:
            xlist = MailList(listname, lock=0)
        # We can mention this list if you already know about it
        if not xlist.advertised and xlist is not mlist:
            continue
        # Skip the list if it isn't in the same virtual domain.  BAW: should a
        # message to the site list include everything regardless of domain?
        if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
               xlist.host_name <> mlist.host_name:
            continue
        realname = xlist.real_name
        description = xlist.description or _('n/a')
        requestaddr = xlist.GetRequestEmail()
        if i > 1:
            res.results.append('')
        res.results.append(_('%(i)3d. List name:   %(realname)s'))
        res.results.append(_('     Description: %(description)s'))
        res.results.append(_('     Requests to: %(requestaddr)s'))
        i += 1
Example #24
0
def _addlist(mlist, fp):
    # Set up the mailman-loop address
    loopaddr = Utils.ParseEmail(Utils.get_site_email(extra='loop'))[0]
    loopmbox = os.path.join(mm_cfg.DATA_DIR, 'owner-bounces.mbox')
    # Seek to the end of the text file, but if it's empty write the standard
    # disclaimer, and the loop catch address.
    fp.seek(0, 2)
    if not fp.tell():
        print >> fp, """\
# This file is generated by Mailman, and is kept in sync with the
# binary hash file aliases.db.  YOU SHOULD NOT MANUALLY EDIT THIS FILE
# unless you know what you're doing, and can keep the two files properly
# in sync.  If you screw it up, you're on your own.
"""
        print >> fp, '# The ultimate loop stopper address'
        print >> fp, '%s: %s' % (loopaddr, loopmbox)
        print >> fp
    # Bootstrapping.  bin/genaliases must be run before any lists are created,
    # but if no lists exist yet then mlist is None.  The whole point of the
    # exercise is to get the minimal aliases.db file into existance.
    if mlist is None:
        return
    listname = mlist.internal_name()
    fieldsz = len(listname) + len('-unsubscribe')
    # The text file entries get a little extra info
    print >> fp, '# STANZA START:', listname
    print >> fp, '# CREATED:', time.ctime(time.time())
    # Now add all the standard alias entries
    for k, v in makealiases(listname):
        # Format the text file nicely
        print >> fp, k + ':', ((fieldsz - len(k)) * ' ') + v
    # Finish the text file stanza
    print >> fp, '# STANZA END:', listname
    print >> fp
Example #25
0
def get_lists(userdesc, perms, vhost, email=None):
    """ List available lists for the given vhost
    """
    if email is None:
        udesc = userdesc
    else:
        udesc = UserDesc(email.lower(), email.lower(), None, 0)
    prefix = vhost.lower()+VHOST_SEP
    names = Utils.list_names()
    names.sort()
    result = []
    for name in names:
        if not name.startswith(prefix):
            continue
        try:
            mlist = MailList.MailList(name, lock=0)
        except:
            continue
        try:
            details = get_list_info(udesc, perms, mlist, (email is None and vhost == PLATAL_DOMAIN))
            if details is not None:
                result.append(details[0])
        except Exception, e:
            sys.stderr.write('Can\'t get list %s: %s\n' % (name, str(e)))
            continue
Example #26
0
def decorate(mlist, template, what, extradict=None):
    # `what' is just a descriptive phrase used in the log message
    #
    # BAW: We've found too many situations where Python can be fooled into
    # interpolating too much revealing data into a format string.  For
    # example, a footer of "% silly %(real_name)s" would give a header
    # containing all list attributes.  While we've previously removed such
    # really bad ones like `password' and `passwords', it's much better to
    # provide a whitelist of known good attributes, then to try to remove a
    # blacklist of known bad ones.
    d = SafeDict({'real_name'     : mlist.real_name,
                  'list_name'     : mlist.internal_name(),
                  # For backwards compatibility
                  '_internal_name': mlist.internal_name(),
                  'host_name'     : mlist.host_name,
                  'web_page_url'  : mlist.web_page_url,
                  'description'   : mlist.description,
                  'info'          : mlist.info,
                  'cgiext'        : mm_cfg.CGIEXT,
                  })
    if extradict is not None:
        d.update(extradict)
    # Using $-strings?
    if getattr(mlist, 'use_dollar_strings', 0):
        template = Utils.to_percent(template)
    # Interpolate into the template
    try:
        text = re.sub(r'(?m)(?<!^--) +(?=\n)', '',
                      re.sub(r'\r\n', r'\n', template % d))
    except (ValueError, TypeError), e:
        syslog('error', 'Exception while calculating %s:\n%s', what, e)
        text = template
Example #27
0
def _addvirtual(mlist, fp):
    listname = mlist.internal_name()
    fieldsz = len(listname) + len('-unsubscribe')
    hostname = mlist.host_name
    # Set up the mailman-loop address
    loopaddr = Utils.get_site_email(mlist.host_name, extra='loop')
    loopdest = Utils.ParseEmail(loopaddr)[0]
    # Seek to the end of the text file, but if it's empty write the standard
    # disclaimer, and the loop catch address.
    fp.seek(0, 2)
    if not fp.tell():
        print >> fp, """\
# This file is generated by Mailman, and is kept in sync with the binary hash
# file virtual-mailman.db.  YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you
# know what you're doing, and can keep the two files properly in sync.  If you
# screw it up, you're on your own.
#
# Note that you should already have this virtual domain set up properly in
# your Postfix installation.  See README.POSTFIX for details.

# LOOP ADDRESSES START
%s\t%s
# LOOP ADDRESSES END
""" % (loopaddr, loopdest)
    # The text file entries get a little extra info
    print >> fp, '# STANZA START:', listname
    print >> fp, '# CREATED:', time.ctime(time.time())
    # Now add all the standard alias entries
    for k, v in makealiases(listname):
        fqdnaddr = '%s@%s' % (k, hostname)
        # Format the text file nicely
        print >> fp, fqdnaddr, ((fieldsz - len(k)) * ' '), k
    # Finish the text file stanza
    print >> fp, '# STANZA END:', listname
    print >> fp
Example #28
0
 def __init__ (self):
     HTMLAction.__init__(self, "ctl-listadmin.html")
     self._ln = self.cgival('lc_name').lower()
     self._priv = self.cgival('lc_private') != ''
     self._safelin = Utils.websafe(self.ln)
     self._pw = mm_cfg.SSO_STOCK_ADMIN_PWD
     self._owner = self.get_owners()
     self._hn = Utils.get_domain()
     self._eh = mm_cfg.VIRTUAL_HOSTS.get(self.hn, mm_cfg.DEFAULT_EMAIL_HOST)
     self._ml = None
     self._langs = [mm_cfg.DEFAULT_SERVER_LANGUAGE]
     self._moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION
     self._notify = 1
     self._info = self.cgival('lc_info')
     self._welcome = self.cgival('lc_welcome')
     self._desc = self.cgival('lc_desc')
Example #29
0
def kill(userdesc, perms, vhost, forlife, promo, del_from_promo):
    """ Remove a user from all the lists.

    Args:
        forlife: the user's forlife email
        promo: the user's promo, if any (format: X2006)
        del_from_promo: bool, whether to delete from the promo lists as well.
    """
    exclude = []
    if promo and promo[0] == 'X' and not del_from_promo:
        exclude.append(PLATAL_DOMAIN + VHOST_SEP + 'promo' + promo[1:])
    for list in Utils.list_names():
        if list in exclude:
            continue
        try:
            mlist = MailList.MailList(list, lock=0)
        except:
            continue
        try:
            mlist.Lock()
            mlist.ApprovedDeleteMember(forlife, None, 0, 0)
            mlist.Save()
            mlist.Unlock()
        except:
            mlist.Unlock()
    return 1
Example #30
0
    def handler (self, parts):
        lists = []
        for name, mlist in self.all_mls.iteritems():
            members = mlist.getRegularMemberKeys()
            subscribed = True if self.curr_user in members else False

            if not mlist.advertised and not subscribed:
                continue

            lists.append({'script_url'  : mlist.GetScriptURL('listinfo'),
                          'real_name'   : mlist.real_name,
                          'description' : Utils.websafe(mlist.description),
                          'subscribed'  : subscribed,
                          'owners'      : ', '.join(mlist.owner),
                          'owner-email' : mlist.GetOwnerEmail(),
                          'advertised'  : mlist.advertised,
                          })
    
        self.kwargs_add('lists', lists)

        if len(parts) > 0:
            try:
                self.add_req_ln_details(parts[0].strip())
            except:
                self.kwargs_add('vl_ln', None)
        else:
            self.kwargs_add('vl_ln', None)

        self.render()
Example #31
0
def loginpage(mlist, doc, user, lang):
    realname = mlist.real_name
    actionurl = mlist.GetScriptURL('options')
    if user is None:
        title = _('%(realname)s list: member options login page')
        extra = _('email address and ')
    else:
        safeuser = Utils.websafe(user)
        title = _('%(realname)s list: member options for user %(safeuser)s')
        obuser = Utils.ObscureEmail(user)
        extra = ''
    # Set up the title
    doc.SetTitle(title)
    # We use a subtable here so we can put a language selection box in
    table = Table(width='100%', border=0, cellspacing=4, cellpadding=5)
    # If only one language is enabled for this mailing list, omit the choice
    # buttons.
    table.AddRow([Center(Header(2, title))])
    table.AddCellInfo(table.GetCurrentRowIndex(), 0,
                      bgcolor=mm_cfg.WEB_HEADER_COLOR)
    if len(mlist.GetAvailableLanguages()) > 1:
        langform = Form(actionurl)
        langform.AddItem(SubmitButton('displang-button',
                                      _('View this page in')))
        langform.AddItem(mlist.GetLangSelectBox(lang))
        if user:
            langform.AddItem(Hidden('email', user))
        table.AddRow([Center(langform)])
    doc.AddItem(table)
    # Preamble
    # Set up the login page
    form = Form(actionurl)
    form.AddItem(Hidden('language', lang))
    table = Table(width='100%', border=0, cellspacing=4, cellpadding=5)
    table.AddRow([_("""In order to change your membership option, you must
    first log in by giving your %(extra)smembership password in the section
    below.  If you don't remember your membership password, you can have it
    emailed to you by clicking on the button below.  If you just want to
    unsubscribe from this list, click on the <em>Unsubscribe</em> button and a
    confirmation message will be sent to you.

    <p><strong><em>Important:</em></strong> From this point on, you must have
    cookies enabled in your browser, otherwise none of your changes will take
    effect.
    """)])
    # Password and login button
    ptable = Table(width='50%', border=0, cellspacing=4, cellpadding=5)
    if user is None:
        ptable.AddRow([Label(_('Email address:')),
                       TextBox('email', size=20)])
    else:
        ptable.AddRow([Hidden('email', user)])
    ptable.AddRow([Label(_('Password:'******'password', size=20)])
    ptable.AddRow([Center(SubmitButton('login', _('Log in')))])
    ptable.AddCellInfo(ptable.GetCurrentRowIndex(), 0, colspan=2)
    table.AddRow([Center(ptable)])
    # Unsubscribe section
    table.AddRow([Center(Header(2, _('Unsubscribe')))])
    table.AddCellInfo(table.GetCurrentRowIndex(), 0,
                      bgcolor=mm_cfg.WEB_HEADER_COLOR)

    table.AddRow([_("""By clicking on the <em>Unsubscribe</em> button, a
    confirmation message will be emailed to you.  This message will have a
    link that you should click on to complete the removal process (you can
    also confirm by email; see the instructions in the confirmation
    message).""")])

    table.AddRow([Center(SubmitButton('login-unsub', _('Unsubscribe')))])
    # Password reminder section
    table.AddRow([Center(Header(2, _('Password reminder')))])
    table.AddCellInfo(table.GetCurrentRowIndex(), 0,
                      bgcolor=mm_cfg.WEB_HEADER_COLOR)

    table.AddRow([_("""By clicking on the <em>Remind</em> button, your
    password will be emailed to you.""")])

    table.AddRow([Center(SubmitButton('login-remind', _('Remind')))])
    # Finish up glomming together the login page
    form.AddItem(table)
    doc.AddItem(form)
    doc.AddItem(mlist.GetMailmanFooter())
Example #32
0
        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)

    # 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)
Example #33
0
def options_page(mlist, doc, user, cpuser, userlang, message=''):
    # The bulk of the document will come from the options.html template, which
    # includes it's own html armor (head tags, etc.).  Suppress the head that
    # Document() derived pages get automatically.
    doc.suppress_head = 1

    if mlist.obscure_addresses:
        presentable_user = Utils.ObscureEmail(user, for_text=1)
        if cpuser is not None:
            cpuser = Utils.ObscureEmail(cpuser, for_text=1)
    else:
        presentable_user = user

    fullname = Utils.uncanonstr(mlist.getMemberName(user), userlang)
    if fullname:
        presentable_user += ', %s' % Utils.websafe(fullname)

    # Do replacements
    replacements = mlist.GetStandardReplacements(userlang)
    replacements['<mm-results>'] = Bold(FontSize('+1', message)).Format()
    replacements['<mm-digest-radio-button>'] = mlist.FormatOptionButton(
        mm_cfg.Digests, 1, user)
    replacements['<mm-undigest-radio-button>'] = mlist.FormatOptionButton(
        mm_cfg.Digests, 0, user)
    replacements['<mm-plain-digests-button>'] = mlist.FormatOptionButton(
        mm_cfg.DisableMime, 1, user)
    replacements['<mm-mime-digests-button>'] = mlist.FormatOptionButton(
        mm_cfg.DisableMime, 0, user)
    replacements['<mm-global-mime-button>'] = (
        CheckBox('mime-globally', 1, checked=0).Format())
    replacements['<mm-delivery-enable-button>'] = mlist.FormatOptionButton(
        mm_cfg.DisableDelivery, 0, user)
    replacements['<mm-delivery-disable-button>'] = mlist.FormatOptionButton(
        mm_cfg.DisableDelivery, 1, user)
    replacements['<mm-disabled-notice>'] = mlist.FormatDisabledNotice(user)
    replacements['<mm-dont-ack-posts-button>'] = mlist.FormatOptionButton(
        mm_cfg.AcknowledgePosts, 0, user)
    replacements['<mm-ack-posts-button>'] = mlist.FormatOptionButton(
        mm_cfg.AcknowledgePosts, 1, user)
    replacements['<mm-receive-own-mail-button>'] = mlist.FormatOptionButton(
        mm_cfg.DontReceiveOwnPosts, 0, user)
    replacements['<mm-dont-receive-own-mail-button>'] = (
        mlist.FormatOptionButton(mm_cfg.DontReceiveOwnPosts, 1, user))
    replacements['<mm-dont-get-password-reminder-button>'] = (
        mlist.FormatOptionButton(mm_cfg.SuppressPasswordReminder, 1, user))
    replacements['<mm-get-password-reminder-button>'] = (
        mlist.FormatOptionButton(mm_cfg.SuppressPasswordReminder, 0, user))
    replacements['<mm-public-subscription-button>'] = (
        mlist.FormatOptionButton(mm_cfg.ConcealSubscription, 0, user))
    replacements['<mm-hide-subscription-button>'] = mlist.FormatOptionButton(
        mm_cfg.ConcealSubscription, 1, user)
    replacements['<mm-dont-receive-duplicates-button>'] = (
        mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 1, user))
    replacements['<mm-receive-duplicates-button>'] = (
        mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 0, user))
    replacements['<mm-unsubscribe-button>'] = (
        mlist.FormatButton('unsub', _('Unsubscribe')) + '<br>' +
        CheckBox('unsubconfirm', 1, checked=0).Format() +
        _('<em>Yes, I really want to unsubscribe</em>'))
    replacements['<mm-new-pass-box>'] = mlist.FormatSecureBox('newpw')
    replacements['<mm-confirm-pass-box>'] = mlist.FormatSecureBox('confpw')
    replacements['<mm-change-pass-button>'] = (
        mlist.FormatButton('changepw', _("Change My Password")))
    replacements['<mm-other-subscriptions-submit>'] = (
        mlist.FormatButton('othersubs',
                           _('List my other subscriptions')))
    replacements['<mm-form-start>'] = (
        mlist.FormatFormStart('options', user))
    replacements['<mm-user>'] = user
    replacements['<mm-presentable-user>'] = presentable_user
    replacements['<mm-email-my-pw>'] = mlist.FormatButton(
        'emailpw', (_('Email My Password To Me')))
    replacements['<mm-umbrella-notice>'] = (
        mlist.FormatUmbrellaNotice(user, _("password")))
    replacements['<mm-logout-button>'] = (
        mlist.FormatButton('logout', _('Log out')))
    replacements['<mm-options-submit-button>'] = mlist.FormatButton(
        'options-submit', _('Submit My Changes'))
    replacements['<mm-global-pw-changes-button>'] = (
        CheckBox('pw-globally', 1, checked=0).Format())
    replacements['<mm-global-deliver-button>'] = (
        CheckBox('deliver-globally', 1, checked=0).Format())
    replacements['<mm-global-remind-button>'] = (
        CheckBox('remind-globally', 1, checked=0).Format())
    replacements['<mm-global-nodupes-button>'] = (
        CheckBox('nodupes-globally', 1, checked=0).Format())

    days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1))
    if days > 1:
        units = _('days')
    else:
        units = _('day')
    replacements['<mm-pending-days>'] = _('%(days)d %(units)s')

    replacements['<mm-new-address-box>'] = mlist.FormatBox('new-address')
    replacements['<mm-confirm-address-box>'] = mlist.FormatBox(
        'confirm-address')
    replacements['<mm-change-address-button>'] = mlist.FormatButton(
        'change-of-address', _('Change My Address and Name'))
    replacements['<mm-global-change-of-address>'] = CheckBox(
        'changeaddr-globally', 1, checked=0).Format()
    replacements['<mm-fullname-box>'] = mlist.FormatBox(
        'fullname', value=fullname)

    # Create the topics radios.  BAW: what if the list admin deletes a topic,
    # but the user still wants to get that topic message?
    usertopics = mlist.getMemberTopics(user)
    if mlist.topics:
        table = Table(border="0")
        for name, pattern, description, emptyflag in mlist.topics:
            if emptyflag:
                continue
            quotedname = urllib.quote_plus(name)
            details = Link(mlist.GetScriptURL('options') +
                           '/%s/?VARHELP=%s' % (user, quotedname),
                           ' (Details)')
            if name in usertopics:
                checked = 1
            else:
                checked = 0
            table.AddRow([CheckBox('usertopic', quotedname, checked=checked),
                          name + details.Format()])
        topicsfield = table.Format()
    else:
        topicsfield = _('<em>No topics defined</em>')
    replacements['<mm-topics>'] = topicsfield
    replacements['<mm-suppress-nonmatching-topics>'] = (
        mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 0, user))
    replacements['<mm-receive-nonmatching-topics>'] = (
        mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 1, user))

    if cpuser is not None:
        replacements['<mm-case-preserved-user>'] = _('''
You are subscribed to this list with the case-preserved address
<em>%(cpuser)s</em>.''')
    else:
        replacements['<mm-case-preserved-user>'] = ''

    doc.AddItem(mlist.ParseTags('options.html', replacements, userlang))
Example #34
0
 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)
Example #35
0
         Center(
             CheckBox('discardalldefersp', 0).Format() + '&nbsp;' +
             _('Discard all messages marked <em>Defer</em>')))
 # 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':
Example #36
0
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() +
                '&nbsp;' +
                _('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() +
                    '&nbsp;' +
                    _('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()
Example #37
0
def show_helds_overview(mlist, form, ssort=SSENDER):
    # Sort the held messages.
    byskey = helds_by_skey(mlist, ssort)
    if not byskey:
        return 0
    form.AddItem('<hr>')
    form.AddItem(Center(Header(2, _('Held Messages'))))
    # Add the sort sequence choices if wanted
    if mm_cfg.DISPLAY_HELD_SUMMARY_SORT_BUTTONS:
        form.AddItem(Center(_('Show this list grouped/sorted by')))
        form.AddItem(Center(hacky_radio_buttons(
                'summary_sort',
                (_('sender/sender'), _('sender/time'), _('ungrouped/time')),
                (SSENDER, SSENDERTIME, STIME),
                (ssort == SSENDER, ssort == SSENDERTIME, ssort == STIME))))
    # Add the by-sender overview tables
    admindburl = mlist.GetScriptURL('admindb', absolute=1)
    table = Table(border=0)
    form.AddItem(table)
    skeys = list(byskey.keys())
    skeys.sort()
    for skey in skeys:
        sender = skey[1]
        qsender = quote_plus(sender)
        esender = Utils.websafe(sender)
        senderurl = admindburl + '?sender=' + qsender
        # The encompassing sender table
        stable = Table(border=1)
        stable.AddRow([Center(Bold(_('From:')).Format() + esender)])
        stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2)
        left = Table(border=0)
        left.AddRow([_('Action to take on all these held messages:')])
        left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
        btns = hacky_radio_buttons(
            'senderaction-' + qsender,
            (_('Defer'), _('Accept'), _('Reject'), _('Discard')),
            (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD),
            (1, 0, 0, 0))
        left.AddRow([btns])
        left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
        left.AddRow([
            '<label>' +
            CheckBox('senderpreserve-' + qsender, 1).Format() +
            '&nbsp;' +
            _('Preserve messages for the site administrator') +
            '</label>'
            ])
        left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
        left.AddRow([
            '<label>' +
            CheckBox('senderforward-' + qsender, 1).Format() +
            '&nbsp;' +
            _('Forward messages (individually) to:') +
            '</label>'
            ])
        left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
        left.AddRow([
            TextBox('senderforwardto-' + qsender,
                    value=mlist.GetOwnerEmail())
            ])
        left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
        # If the sender is a member and the message is being held due to a
        # moderation bit, give the admin a chance to clear the member's mod
        # bit.  If this sender is not a member and is not already on one of
        # the sender filters, then give the admin a chance to add this sender
        # to one of the filters.
        if mlist.isMember(sender):
            if mlist.getMemberOption(sender, mm_cfg.Moderate):
                left.AddRow([
                    '<label>' +
                    CheckBox('senderclearmodp-' + qsender, 1).Format() +
                    '&nbsp;' +
                    _("Clear this member's <em>moderate</em> flag") +
                    '</label>'
                    ])
            else:
                left.AddRow(
                    [_('<em>The sender is now a member of this list</em>')])
            left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
        elif sender not in (mlist.accept_these_nonmembers +
                            mlist.hold_these_nonmembers +
                            mlist.reject_these_nonmembers +
                            mlist.discard_these_nonmembers):
            left.AddRow([
                '<label>' +
                CheckBox('senderfilterp-' + qsender, 1).Format() +
                '&nbsp;' +
                _('Add <b>%(esender)s</b> to one of these sender filters:') +
                '</label>'
                ])
            left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
            btns = hacky_radio_buttons(
                'senderfilter-' + qsender,
                (_('Accepts'), _('Holds'), _('Rejects'), _('Discards')),
                (mm_cfg.ACCEPT, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD),
                (0, 0, 0, 1))
            left.AddRow([btns])
            left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
            if sender not in mlist.ban_list:
                left.AddRow([
                    '<label>' +
                    CheckBox('senderbanp-' + qsender, 1).Format() +
                    '&nbsp;' +
                    _("""Ban <b>%(esender)s</b> from ever subscribing to this
                    mailing list""") + '</label>'])
                left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
        right = Table(border=0)
        right.AddRow([
            _("""Click on the message number to view the individual
            message, or you can """) +
            Link(senderurl, _('view all messages from %(esender)s')).Format()
            ])
        right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2)
        right.AddRow(['&nbsp;', '&nbsp;'])
        counter = 1
        for ptime, id in byskey[skey]:
            info = mlist.GetRecord(id)
            ptime, sender, subject, reason, filename, msgdata = info
            # BAW: This is really the size of the message pickle, which should
            # be close, but won't be exact.  Sigh, good enough.
            try:
                size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename))
            except OSError as e:
                if e.errno != errno.ENOENT: raise
                # This message must have gotten lost, i.e. it's already been
                # handled by the time we got here.
                mlist.HandleRequest(id, mm_cfg.DISCARD)
                continue
            dispsubj = Utils.oneline(
                subject, Utils.GetCharSet(mlist.preferred_language))
            t = Table(border=0)
            t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter),
                      Bold(_('Subject:')),
                      Utils.websafe(dispsubj)
                      ])
            t.AddRow(['&nbsp;', Bold(_('Size:')), str(size) + _(' bytes')])
            if reason:
                reason = _(reason)
            else:
                reason = _('not available')
            t.AddRow(['&nbsp;', Bold(_('Reason:')), reason])
            # Include the date we received the message, if available
            when = msgdata.get('received_time')
            if when:
                t.AddRow(['&nbsp;', Bold(_('Received:')),
                          time.ctime(when)])
            t.AddRow([InputObj(qsender, 'hidden', str(id), False).Format()])
            counter += 1
            right.AddRow([t])
        stable.AddRow([left, right])
        table.AddRow([stable])
    return 1
Example #38
0
def show_post_requests(mlist, id, info, total, count, form):
    # Mailman.ListAdmin.__handlepost no longer tests for pre 2.0beta3
    ptime, sender, subject, reason, filename, msgdata = info
    form.AddItem('<hr>')
    # Header shown on each held posting (including count of total)
    msg = _('Posting Held for Approval')
    if total != 1:
        msg += _(' (%(count)d of %(total)d)')
    form.AddItem(Center(Header(2, msg)))
    # We need to get the headers and part of the textual body of the message
    # being held.  The best way to do this is to use the email.parser to get
    # an actual object, which will be easier to deal with.  We probably could
    # just do raw reads on the file.
    try:
        msg = readMessage(os.path.join(mm_cfg.DATA_DIR, filename))
    except IOError as e:
        if e.errno != errno.ENOENT:
            raise
        form.AddItem(_('<em>Message with id #%(id)d was lost.'))
        form.AddItem('<p>')
        # BAW: kludge to remove id from requests.db.
        try:
            mlist.HandleRequest(id, mm_cfg.DISCARD)
        except Errors.LostHeldMessage:
            pass
        return
    except email.errors.MessageParseError:
        form.AddItem(_('<em>Message with id #%(id)d is corrupted.'))
        # BAW: Should we really delete this, or shuttle it off for site admin
        # to look more closely at?
        form.AddItem('<p>')
        # BAW: kludge to remove id from requests.db.
        try:
            mlist.HandleRequest(id, mm_cfg.DISCARD)
        except Errors.LostHeldMessage:
            pass
        return
    # Get the header text and the message body excerpt
    lines = []
    chars = 0
    # A negative value means, include the entire message regardless of size
    limit = mm_cfg.ADMINDB_PAGE_TEXT_LIMIT
    for line in email.iterators.body_line_iterator(msg, decode=True):
        lines.append(line)
        chars += len(line)
        if chars >= limit > 0:
            break
    # We may have gone over the limit on the last line, but keep the full line
    # anyway to avoid losing part of a multibyte character.
    body = EMPTYSTRING.join(lines)
    # Get message charset and try encode in list charset
    # We get it from the first text part.
    # We need to replace invalid characters here or we can throw an uncaught
    # exception in doc.Format().
    for part in msg.walk():
        if part.get_content_maintype() == 'text':
            # Watchout for charset= with no value.
            mcset = part.get_content_charset() or 'us-ascii'
            break
    else:
        mcset = 'us-ascii'
    lcset = Utils.GetCharSet(mlist.preferred_language)
    if mcset != lcset:
        try:
            body = str(body, mcset, 'replace')
        except (LookupError, UnicodeError, ValueError):
            pass
    hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in list(msg.items())])
    hdrtxt = Utils.websafe(hdrtxt)
    # Okay, we've reconstituted the message just fine.  Now for the fun part!
    t = Table(cellspacing=0, cellpadding=0, width='100%')
    t.AddRow([Bold(_('From:')), sender])
    row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
    t.AddCellInfo(row, col-1, align='right')
    t.AddRow([Bold(_('Subject:')),
              Utils.websafe(Utils.oneline(subject, lcset))])
    t.AddCellInfo(row+1, col-1, align='right')
    t.AddRow([Bold(_('Reason:')), _(reason)])
    t.AddCellInfo(row+2, col-1, align='right')
    when = msgdata.get('received_time')
    if when:
        t.AddRow([Bold(_('Received:')), time.ctime(when)])
        t.AddCellInfo(row+3, col-1, align='right')
    buttons = hacky_radio_buttons(id,
                (_('Defer'), _('Approve'), _('Reject'), _('Discard')),
                (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD),
                (1, 0, 0, 0),
                spacing=5)
    t.AddRow([Bold(_('Action:')), buttons])
    t.AddCellInfo(t.GetCurrentRowIndex(), col-1, align='right')
    t.AddRow(['&nbsp;',
              '<label>' +
              CheckBox('preserve-%d' % id, 'on', 0).Format() +
              '&nbsp;' + _('Preserve message for site administrator') +
              '</label>'
              ])
    t.AddRow(['&nbsp;',
              '<label>' +
              CheckBox('forward-%d' % id, 'on', 0).Format() +
              '&nbsp;' + _('Additionally, forward this message to: ') +
              '</label>' +
              TextBox('forward-addr-%d' % id, size=47,
                      value=mlist.GetOwnerEmail()).Format()
              ])
    notice = msgdata.get('rejection_notice', _('[No explanation given]'))
    t.AddRow([
        Bold(_('If you reject this post,<br>please explain (optional):')),
        TextArea('comment-%d' % id, rows=4, cols=EXCERPT_WIDTH,
                 text = Utils.wrap(_(notice), column=80))
        ])
    row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
    t.AddCellInfo(row, col-1, align='right')
    t.AddRow([Bold(_('Message Headers:')),
              TextArea('headers-%d' % id, hdrtxt,
                       rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)])
    row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
    t.AddCellInfo(row, col-1, align='right')
    t.AddRow([Bold(_('Message Excerpt:')),
              TextArea('fulltext-%d' % id, Utils.websafe(body),
                       rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)])
    t.AddCellInfo(row+1, col-1, align='right')
    form.AddItem(t)
    form.AddItem('<p>')
Example #39
0
def process_form(mlist, doc, cgidata):
    global ssort
    senderactions = {}
    badaddrs = []
    # Sender-centric actions
    for k in list(cgidata.keys()):
        for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-',
                       'senderforwardto-', 'senderfilterp-', 'senderfilter-',
                       'senderclearmodp-', 'senderbanp-'):
            if k.startswith(prefix):
                action = k[:len(prefix)-1]
                qsender = k[len(prefix):]
                sender = unquote_plus(qsender)
                value = cgidata.getfirst(k)
                senderactions.setdefault(sender, {})[action] = value
                for id in cgidata.getlist(qsender):
                    senderactions[sender].setdefault('message_ids',
                                                     []).append(int(id))
    # discard-all-defers
    try:
        discardalldefersp = cgidata.getfirst('discardalldefersp', 0)
    except ValueError:
        discardalldefersp = 0
    # Get the summary sequence
    ssort = int(cgidata.getfirst('summary_sort', SSENDER))
    for sender in list(senderactions.keys()):
        actions = senderactions[sender]
        # Handle what to do about all this sender's held messages
        try:
            action = int(actions.get('senderaction', mm_cfg.DEFER))
        except ValueError:
            action = mm_cfg.DEFER
        if action == mm_cfg.DEFER and discardalldefersp:
            action = mm_cfg.DISCARD
        if action in (mm_cfg.DEFER, mm_cfg.APPROVE,
                      mm_cfg.REJECT, mm_cfg.DISCARD):
            preserve = actions.get('senderpreserve', 0)
            forward = actions.get('senderforward', 0)
            forwardaddr = actions.get('senderforwardto', '')
            byskey = helds_by_skey(mlist, SSENDER)
            for ptime, id in byskey.get((0, sender), []):
                if id not in senderactions[sender]['message_ids']:
                    # It arrived after the page was displayed. Skip it.
                    continue
                try:
                    msgdata = mlist.GetRecord(id)[5]
                    comment = msgdata.get('rejection_notice',
                                      _('[No explanation given]'))
                    mlist.HandleRequest(id, action, comment, preserve,
                                        forward, forwardaddr)
                except (KeyError, Errors.LostHeldMessage):
                    # That's okay, it just means someone else has already
                    # updated the database while we were staring at the page,
                    # so just ignore it
                    continue
        # Now see if this sender should be added to one of the nonmember
        # sender filters.
        if actions.get('senderfilterp', 0):
            # Check for an invalid sender address.
            try:
                Utils.ValidateEmail(sender)
            except Errors.EmailAddressError:
                # Don't check for dups.  Report it once for each checked box.
                badaddrs.append(sender)
            else:
                try:
                    which = int(actions.get('senderfilter'))
                except ValueError:
                    # Bogus form
                    which = 'ignore'
                if which == mm_cfg.ACCEPT:
                    mlist.accept_these_nonmembers.append(sender)
                elif which == mm_cfg.HOLD:
                    mlist.hold_these_nonmembers.append(sender)
                elif which == mm_cfg.REJECT:
                    mlist.reject_these_nonmembers.append(sender)
                elif which == mm_cfg.DISCARD:
                    mlist.discard_these_nonmembers.append(sender)
                # Otherwise, it's a bogus form, so ignore it
        # And now see if we're to clear the member's moderation flag.
        if actions.get('senderclearmodp', 0):
            try:
                mlist.setMemberOption(sender, mm_cfg.Moderate, 0)
            except Errors.NotAMemberError:
                # This person's not a member any more.  Oh well.
                pass
        # And should this address be banned?
        if actions.get('senderbanp', 0):
            # Check for an invalid sender address.
            try:
                Utils.ValidateEmail(sender)
            except Errors.EmailAddressError:
                # Don't check for dups.  Report it once for each checked box.
                badaddrs.append(sender)
            else:
                if sender not in mlist.ban_list:
                    mlist.ban_list.append(sender)
    # Now, do message specific actions
    banaddrs = []
    erroraddrs = []
    for k in list(cgidata.keys()):
        formv = cgidata[k]
        if isinstance(formv, list):
            continue
        try:
            v = int(formv.value)
            request_id = int(k)
        except ValueError:
            continue
        if v not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT,
                     mm_cfg.DISCARD, mm_cfg.SUBSCRIBE, mm_cfg.UNSUBSCRIBE,
                     mm_cfg.ACCEPT, mm_cfg.HOLD):
            continue
        # Get the action comment and reasons if present.
        commentkey = 'comment-%d' % request_id
        preservekey = 'preserve-%d' % request_id
        forwardkey = 'forward-%d' % request_id
        forwardaddrkey = 'forward-addr-%d' % request_id
        bankey = 'ban-%d' % request_id
        # Defaults
        try:
            if mlist.GetRecordType(request_id) == HELDMSG:
                msgdata = mlist.GetRecord(request_id)[5]
                comment = msgdata.get('rejection_notice',
                                      _('[No explanation given]'))
            else:
                comment = _('[No explanation given]')
        except KeyError:
            # Someone else must have handled this one after we got the page.
            continue
        preserve = 0
        forward = 0
        forwardaddr = ''
        if commentkey in cgidata:
            comment = cgidata[commentkey].value
        if preservekey in cgidata:
            preserve = cgidata[preservekey].value
        if forwardkey in cgidata:
            forward = cgidata[forwardkey].value
        if forwardaddrkey in cgidata:
            forwardaddr = cgidata[forwardaddrkey].value
        # Should we ban this address?  Do this check before handling the
        # request id because that will evict the record.
        if cgidata.getfirst(bankey):
            sender = mlist.GetRecord(request_id)[1]
            if sender not in mlist.ban_list:
                # We don't need to validate the sender.  An invalid address
                # can't get here.
                mlist.ban_list.append(sender)
        # Handle the request id
        try:
            mlist.HandleRequest(request_id, v, comment,
                                preserve, forward, forwardaddr)
        except (KeyError, Errors.LostHeldMessage):
            # That's okay, it just means someone else has already updated the
            # database while we were staring at the page, so just ignore it
            continue
        except Errors.MMAlreadyAMember as v:
            erroraddrs.append(v)
        except Errors.MembershipIsBanned as pattern:
            sender = mlist.GetRecord(request_id)[1]
            banaddrs.append((sender, pattern))
    # save the list and print the results
    doc.AddItem(Header(2, _('Database Updated...')))
    if erroraddrs:
        for addr in erroraddrs:
            addr = Utils.websafe(addr)
            doc.AddItem(repr(addr) + _(' is already a member') + '<br>')
    if banaddrs:
        for addr, patt in banaddrs:
            addr = Utils.websafe(addr)
            doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '<br>')
    if badaddrs:
        for addr in badaddrs:
            addr = Utils.websafe(addr)
            doc.AddItem(repr(addr) + ': ' + _('Bad/Invalid email address') +
                        '<br>')
Example #40
0
    def Authenticate(self, authcontexts, response, user=None):
        # Given a list of authentication contexts, check to see if the
        # response matches one of the passwords.  authcontexts must be a
        # sequence, and if it contains the context AuthUser, then the user
        # argument must not be None.
        #
        # Return the authcontext from the argument sequence that matches the
        # response, or UnAuthorized.
        for ac in authcontexts:
            if ac == mm_cfg.AuthCreator:
                ok = Utils.check_global_password(response, siteadmin=0)
                if ok:
                    return mm_cfg.AuthCreator
            elif ac == mm_cfg.AuthSiteAdmin:
                ok = Utils.check_global_password(response)
                if ok:
                    return mm_cfg.AuthSiteAdmin
            elif ac == mm_cfg.AuthListAdmin:

                def cryptmatchp(response, secret):
                    try:
                        salt = secret[:2]
                        if crypt and crypt.crypt(response, salt) == secret:
                            return True
                        return False
                    except TypeError:
                        # BAW: Hard to say why we can get a TypeError here.
                        # SF bug report #585776 says crypt.crypt() can raise
                        # this if salt contains null bytes, although I don't
                        # know how that can happen (perhaps if a MM2.0 list
                        # with USE_CRYPT = 0 has been updated?  Doubtful.
                        return False

                # The password for the list admin and list moderator are not
                # kept as plain text, but instead as an sha hexdigest.  The
                # response being passed in is plain text, so we need to
                # digestify it first.  Note however, that for backwards
                # compatibility reasons, we'll also check the admin response
                # against the crypted and md5'd passwords, and if they match,
                # we'll auto-migrate the passwords to sha.
                key, secret = self.AuthContextInfo(ac)
                if secret is None:
                    continue
                sharesponse = sha_new(response).hexdigest()
                upgrade = ok = False
                if sharesponse == secret:
                    ok = True
                elif md5_new(response).digest() == secret:
                    ok = upgrade = True
                elif cryptmatchp(response, secret):
                    ok = upgrade = True
                if upgrade:
                    save_and_unlock = False
                    if not self.__mlist.Locked():
                        self.__mlist.Lock()
                        save_and_unlock = True
                    try:
                        self.__mlist.password = sharesponse
                        if save_and_unlock:
                            self.__mlist.Save()
                    finally:
                        if save_and_unlock:
                            self.__mlist.Unlock()
                if ok:
                    return ac
            elif ac == mm_cfg.AuthListModerator:
                # The list moderator password must be sha'd
                key, secret = self.AuthContextInfo(ac)
                if secret and sha_new(response).hexdigest() == secret:
                    return ac
            elif ac == mm_cfg.AuthUser:
                if user is not None:
                    try:
                        if self.__mlist.authenticateMember(user, response):
                            return ac
                    except Errors.NotAMemberError:
                        pass
            else:
                # What is this context???
                syslog('error', 'Bad authcontext: %s', ac)
                raise ValueError, 'Bad authcontext: %s' % ac
        return mm_cfg.UnAuthorized
Example #41
0
    # 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),
Example #42
0
class Archiver:
    #
    # Interface to Pipermail.  HyperArch.py uses this method to get the
    # archive directory for the mailing list
    #
    def InitVars(self):
        # Configurable
        self.archive = mm_cfg.DEFAULT_ARCHIVE
        # 0=public, 1=private:
        self.archive_private = mm_cfg.DEFAULT_ARCHIVE_PRIVATE
        self.archive_volume_frequency = \
                mm_cfg.DEFAULT_ARCHIVE_VOLUME_FREQUENCY
        # The archive file structure by default is:
        #
        # archives/
        #     private/
        #         listname.mbox/
        #             listname.mbox
        #         listname/
        #             lots-of-pipermail-stuff
        #     public/
        #         listname.mbox@ -> ../private/listname.mbox
        #         listname@ -> ../private/listname
        #
        # IOW, the mbox and pipermail archives are always stored in the
        # private archive for the list.  This is safe because archives/private
        # is always set to o-rx.  Public archives have a symlink to get around
        # the private directory, pointing directly to the private/listname
        # which has o+rx permissions.  Private archives do not have the
        # symbolic links.
        omask = os.umask(0)
        try:
            try:
                os.mkdir(self.archive_dir() + '.mbox', 02775)
            except OSError, e:
                if e.errno <> errno.EEXIST: raise
                # We also create an empty pipermail archive directory into
                # which we'll drop an empty index.html file into.  This is so
                # that lists that have not yet received a posting have
                # /something/ as their index.html, and don't just get a 404.
            try:
                os.mkdir(self.archive_dir(), 02775)
            except OSError, e:
                if e.errno <> errno.EEXIST: raise
            # See if there's an index.html file there already and if not,
            # write in the empty archive notice.
            indexfile = os.path.join(self.archive_dir(), 'index.html')
            fp = None
            try:
                fp = open(indexfile)
            except IOError, e:
                if e.errno <> errno.ENOENT: raise
                omask = os.umask(002)
                try:
                    fp = open(indexfile, 'w')
                finally:
                    os.umask(omask)
                fp.write(
                    Utils.maketext(
                        'emptyarchive.html', {
                            'listname': self.real_name,
                            'listinfo': self.GetScriptURL('listinfo',
                                                          absolute=1),
                        },
                        mlist=self))
Example #43
0
 def handleForm(self, mlist, category, subcat, cgidata, doc):
     # MAS: Did we come from the authentication page?
     if not cgidata.has_key('topic_box_01'):
         return
     topics = []
     # We start i at 1 and keep going until we no longer find items keyed
     # with the marked tags.
     i = 1
     while True:
         deltag = 'topic_delete_%02d' % i
         boxtag = 'topic_box_%02d' % i
         reboxtag = 'topic_rebox_%02d' % i
         desctag = 'topic_desc_%02d' % i
         wheretag = 'topic_where_%02d' % i
         addtag = 'topic_add_%02d' % i
         newtag = 'topic_new_%02d' % i
         i += 1
         # Was this a delete?  If so, we can just ignore this entry
         if cgidata.has_key(deltag):
             continue
         # Get the data for the current box
         name = cgidata.getfirst(boxtag)
         pattern = cgidata.getfirst(reboxtag)
         desc = cgidata.getfirst(desctag)
         if name is None:
             # We came to the end of the boxes
             break
         if cgidata.has_key(newtag) and (not name or not pattern):
             # This new entry is incomplete.
             doc.addError(
                 _("""Topic specifications require both a name and
             a pattern.  Incomplete topics will be ignored."""))
             continue
         # Make sure the pattern was a legal regular expression
         name = Utils.websafe(name)
         try:
             orpattern = OR.join(pattern.splitlines())
             re.compile(orpattern)
         except (re.error, TypeError):
             safepattern = Utils.websafe(orpattern)
             doc.addError(
                 _("""The topic pattern '%(safepattern)s' is not a
             legal regular expression.  It will be discarded."""))
             continue
         # Was this an add item?
         if cgidata.has_key(addtag):
             # Where should the new one be added?
             where = cgidata.getfirst(wheretag)
             if where == 'before':
                 # Add a new empty topics box before the current one
                 topics.append(('', '', '', True))
                 topics.append((name, pattern, desc, False))
                 # Default is to add it after...
             else:
                 topics.append((name, pattern, desc, False))
                 topics.append(('', '', '', True))
         # Otherwise, just retain this one in the list
         else:
             topics.append((name, pattern, desc, False))
     # Add these topics to the mailing list object, and deal with other
     # options.
     mlist.topics = topics
     try:
         mlist.topics_enabled = int(
             cgidata.getfirst('topics_enabled', mlist.topics_enabled))
     except ValueError:
         # BAW: should really print a warning
         pass
     try:
         mlist.topics_bodylines_limit = int(
             cgidata.getfirst('topics_bodylines_limit',
                              mlist.topics_bodylines_limit))
     except ValueError:
         # BAW: should really print a warning
         pass