1# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
2#
3# This program is free software; you can redistribute it and/or
4# modify it under the terms of the GNU General Public License
5# as published by the Free Software Foundation; either version 2
6# of the License, or (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16# USA.
17
18"""Produce listinfo page, primary web entry-point to mailing lists.
19"""
20
21# No lock needed in this script, because we don't change data.
22
23import os
24import cgi
25import time
26
27from Mailman import mm_cfg
28from Mailman import Utils
29from Mailman import MailList
30from Mailman import Errors
31from Mailman import i18n
32from Mailman.htmlformat import *
33from Mailman.Logging.Syslog import syslog
34
35# Set up i18n
36_ = i18n._
37i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
38
39
40
41def main():
42    parts = Utils.GetPathPieces()
43    if not parts:
44        listinfo_overview()
45        return
46
47    listname = parts[0].lower()
48    try:
49        mlist = MailList.MailList(listname, lock=0)
50    except Errors.MMListError, e:
51        # Avoid cross-site scripting attacks
52        safelistname = Utils.websafe(listname)
53        # Send this with a 404 status.
54        print 'Status: 404 Not Found'
55        listinfo_overview(_('No such list <em>%(safelistname)s</em>'))
56        syslog('error', 'listinfo: No such list "%s": %s', listname, e)
57        return
58
59    # See if the user want to see this page in other language
60    cgidata = cgi.FieldStorage()
61    try:
62        language = cgidata.getfirst('language')
63    except TypeError:
64        # Someone crafted a POST with a bad Content-Type:.
65        doc = Document()
66        doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
67        doc.AddItem(Header(2, _("Error")))
68        doc.AddItem(Bold(_('Invalid options to CGI script.')))
69        # Send this with a 400 status.
70        print 'Status: 400 Bad Request'
71        print doc.Format()
72        return
73
74    if not Utils.IsLanguage(language):
75        language = mlist.preferred_language
76    i18n.set_language(language)
77    list_listinfo(mlist, language)
78
79
80
81def listinfo_overview(msg=''):
82    # Present the general listinfo overview
83    hostname = Utils.get_domain()
84    # Set up the document and assign it the correct language.  The only one we
85    # know about at the moment is the server's default.
86    doc = Document()
87    doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
88
89    legend = _("%(hostname)s Mailing Lists")
90    doc.SetTitle(legend)
91
92    table = Table(border=0, width="100%")
93    table.AddRow([Center(Header(2, legend))])
94    table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2,
95                      bgcolor=mm_cfg.WEB_HEADER_COLOR)
96
97    # Skip any mailing lists that isn't advertised.
98    advertised = []
99    listnames = Utils.list_names()
100    listnames.sort()
101
102    for name in listnames:
103        try:
104            mlist = MailList.MailList(name, lock=0)
105        except Errors.MMUnknownListError:
106            # The list could have been deleted by another process.
107            continue
108        if mlist.advertised:
109            if mm_cfg.VIRTUAL_HOST_OVERVIEW and (
110                   mlist.web_page_url.find('/%s/' % hostname) == -1 and
111                   mlist.web_page_url.find('/%s:' % hostname) == -1):
112                # List is for different identity of this host - skip it.
113                continue
114            else:
115                advertised.append((mlist.GetScriptURL('listinfo'),
116                                   mlist.real_name,
117                                   Utils.websafe(mlist.GetDescription())))
118    if msg:
119        greeting = FontAttr(msg, color="ff5060", size="+1")
120    else:
121        greeting = FontAttr(_('Welcome!'), size='+2')
122
123    welcome = [greeting]
124    mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format()
125    if not advertised:
126        welcome.extend(
127            _('''<p>There currently are no publicly-advertised
128            %(mailmanlink)s mailing lists on %(hostname)s.'''))
129    else:
130        welcome.append(
131            _('''<p>Below is a listing of all the public mailing lists on
132            %(hostname)s.  Click on a list name to get more information about
133            the list, or to subscribe, unsubscribe, and change the preferences
134            on your subscription.'''))
135
136    # set up some local variables
137    adj = msg and _('right') or ''
138    siteowner = Utils.get_site_email()
139    welcome.extend(
140        (_(''' To visit the general information page for an unadvertised list,
141        open a URL similar to this one, but with a '/' and the %(adj)s
142        list name appended.
143        <p>List administrators, you can visit '''),
144         Link(Utils.ScriptURL('admin'),
145              _('the list admin overview page')),
146         _(''' to find the management interface for your list.
147         <p>If you are having trouble using the lists, please contact '''),
148         Link('mailto:' + siteowner, siteowner),
149         '.<p>'))
150
151    table.AddRow([apply(Container, welcome)])
152    table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2)
153
154    if advertised:
155        table.AddRow(['&nbsp;', '&nbsp;'])
156        table.AddRow([Bold(FontAttr(_('List'), size='+2')),
157                      Bold(FontAttr(_('Description'), size='+2'))
158                      ])
159        highlight = 1
160        for url, real_name, description in advertised:
161            table.AddRow(
162                [Link(url, Bold(real_name)),
163                      description or Italic(_('[no description available]'))])
164            if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR:
165                table.AddRowInfo(table.GetCurrentRowIndex(),
166                                 bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR)
167            highlight = not highlight
168
169    doc.AddItem(table)
170    doc.AddItem('<hr>')
171    doc.AddItem(MailmanLogo())
172    print doc.Format()
173
174
175
176def list_listinfo(mlist, lang):
177    # Generate list specific listinfo
178    doc = HeadlessDocument()
179    doc.set_language(lang)
180
181    replacements = mlist.GetStandardReplacements(lang)
182
183    if not mlist.digestable or not mlist.nondigestable:
184        replacements['<mm-digest-radio-button>'] = ""
185        replacements['<mm-undigest-radio-button>'] = ""
186        replacements['<mm-digest-question-start>'] = '<!-- '
187        replacements['<mm-digest-question-end>'] = ' -->'
188    else:
189        replacements['<mm-digest-radio-button>'] = mlist.FormatDigestButton()
190        replacements['<mm-undigest-radio-button>'] = \
191                                                   mlist.FormatUndigestButton()
192        replacements['<mm-digest-question-start>'] = ''
193        replacements['<mm-digest-question-end>'] = ''
194    replacements['<mm-plain-digests-button>'] = \
195                                              mlist.FormatPlainDigestsButton()
196    replacements['<mm-mime-digests-button>'] = mlist.FormatMimeDigestsButton()
197    replacements['<mm-subscribe-box>'] = mlist.FormatBox('email', size=30)
198    replacements['<mm-subscribe-button>'] = mlist.FormatButton(
199        'email-button', text=_('Subscribe'))
200    replacements['<mm-new-password-box>'] = mlist.FormatSecureBox('pw')
201    replacements['<mm-confirm-password>'] = mlist.FormatSecureBox('pw-conf')
202    replacements['<mm-subscribe-form-start>'] = mlist.FormatFormStart(
203        'subscribe')
204    if mm_cfg.SUBSCRIBE_FORM_SECRET:
205        now = str(int(time.time()))
206        remote = os.environ.get('HTTP_FORWARDED_FOR',
207                 os.environ.get('HTTP_X_FORWARDED_FOR',
208                 os.environ.get('REMOTE_ADDR',
209                                'w.x.y.z')))
210        # Try to accept a range in case of load balancers, etc.  (LP: #1447445)
211        if remote.find('.') >= 0:
212            # ipv4 - drop last octet
213            remote = remote.rsplit('.', 1)[0]
214        else:
215            # ipv6 - drop last 16 (could end with :: in which case we just
216            #        drop one : resulting in an invalid format, but it's only
217            #        for our hash so it doesn't matter.
218            remote = remote.rsplit(':', 1)[0]
219        # render CAPTCHA, if configured
220        if isinstance(mm_cfg.CAPTCHAS, dict) and 'en' in mm_cfg.CAPTCHAS:
221            (captcha_question, captcha_box, captcha_idx) = \
222                Utils.captcha_display(mlist, lang, mm_cfg.CAPTCHAS)
223            pre_question = _(
224                    """Please answer the following question to prove that
225                    you are not a bot:"""
226                )
227            replacements['<mm-captcha-ui>'] = (
228                """<tr><td BGCOLOR="#dddddd">%s<br>%s</td><td>%s</td></tr>"""
229                % (pre_question, captcha_question, captcha_box))
230        else:
231            # just to have something to include in the hash below
232            captcha_idx = ''
233        # fill form
234        replacements['<mm-subscribe-form-start>'] += (
235                '<input type="hidden" name="sub_form_token"'
236                ' value="%s:%s:%s">\n'
237                % (now, captcha_idx,
238                          Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET + ":" +
239                          now + ":" +
240                          captcha_idx + ":" +
241                          mlist.internal_name() + ":" +
242                          remote
243                          ).hexdigest()
244                    )
245                )
246    # Roster form substitutions
247    replacements['<mm-roster-form-start>'] = mlist.FormatFormStart('roster')
248    replacements['<mm-roster-option>'] = mlist.FormatRosterOptionForUser(lang)
249    # Options form substitutions
250    replacements['<mm-options-form-start>'] = mlist.FormatFormStart('options')
251    replacements['<mm-editing-options>'] = mlist.FormatEditingOption(lang)
252    replacements['<mm-info-button>'] = SubmitButton('UserOptions',
253                                                    _('Edit Options')).Format()
254    # If only one language is enabled for this mailing list, omit the choice
255    # buttons.
256    if len(mlist.GetAvailableLanguages()) == 1:
257        displang = ''
258    else:
259        displang = mlist.FormatButton('displang-button',
260                                      text = _("View this page in"))
261    replacements['<mm-displang-box>'] = displang
262    replacements['<mm-lang-form-start>'] = mlist.FormatFormStart('listinfo')
263    replacements['<mm-fullname-box>'] = mlist.FormatBox('fullname', size=30)
264    # If reCAPTCHA is enabled, display its user interface
265    if mm_cfg.RECAPTCHA_SITE_KEY:
266        noscript = _('This form requires JavaScript.')
267        replacements['<mm-recaptcha-ui>'] = (
268            """<tr><td>&nbsp;</td><td>
269            <noscript>%s</noscript>
270            <script src="https://www.google.com/recaptcha/api.js?hl=%s">
271            </script>
272            <div class="g-recaptcha" data-sitekey="%s"></div>
273            </td></tr>"""
274            % (noscript, lang, mm_cfg.RECAPTCHA_SITE_KEY))
275    else:
276        replacements['<mm-recaptcha-ui>'] = ''
277
278    # Do the expansion.
279    doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang))
280    print doc.Format()
281
282
283
284if __name__ == "__main__":
285    main()
286