patch mailman to add a simple question-and-answer CAPTCHA
[ansible.git] / roles / email / files / mailman-patched / Cgi / listinfo.py
1 # Copyright (C) 1998-2016 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
23 import os
24 import cgi
25 import time
26
27 from Mailman import mm_cfg
28 from Mailman import Utils
29 from Mailman import Captcha # MAILMAN_CAPTCHA_PATCHED
30 from Mailman import MailList
31 from Mailman import Errors
32 from Mailman import i18n
33 from Mailman.htmlformat import *
34 from Mailman.Logging.Syslog import syslog
35
36 # Set up i18n
37 _ = i18n._
38 i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
39
40
41
42
43 def main():
44     parts = Utils.GetPathPieces()
45     if not parts:
46         listinfo_overview()
47         return
48
49     listname = parts[0].lower()
50     try:
51         mlist = MailList.MailList(listname, lock=0)
52     except Errors.MMListError, e:
53         # Avoid cross-site scripting attacks
54         safelistname = Utils.websafe(listname)
55         # Send this with a 404 status.
56         print 'Status: 404 Not Found'
57         listinfo_overview(_('No such list <em>%(safelistname)s</em>'))
58         syslog('error', 'listinfo: No such list "%s": %s', listname, e)
59         return
60
61     # See if the user want to see this page in other language
62     cgidata = cgi.FieldStorage()
63     try:
64         language = cgidata.getvalue('language')
65     except TypeError:
66         # Someone crafted a POST with a bad Content-Type:.
67         doc = Document()
68         doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
69         doc.AddItem(Header(2, _("Error")))
70         doc.AddItem(Bold(_('Invalid options to CGI script.')))
71         # Send this with a 400 status.
72         print 'Status: 400 Bad Request'
73         print doc.Format()
74         return
75
76     if not Utils.IsLanguage(language):
77         language = mlist.preferred_language
78     i18n.set_language(language)
79     list_listinfo(mlist, language)
80
81
82
83
84 def listinfo_overview(msg=''):
85     # Present the general listinfo overview
86     hostname = Utils.get_domain()
87     # Set up the document and assign it the correct language.  The only one we
88     # know about at the moment is the server's default.
89     doc = Document()
90     doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
91
92     legend = _("%(hostname)s Mailing Lists")
93     doc.SetTitle(legend)
94
95     table = Table(border=0, width="100%")
96     table.AddRow([Center(Header(2, legend))])
97     table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2,
98                       bgcolor=mm_cfg.WEB_HEADER_COLOR)
99
100     # Skip any mailing lists that isn't advertised.
101     advertised = []
102     listnames = Utils.list_names()
103     listnames.sort()
104
105     for name in listnames:
106         try:
107             mlist = MailList.MailList(name, lock=0)
108         except Errors.MMUnknownListError:
109             # The list could have been deleted by another process.
110             continue
111         if mlist.advertised:
112             if mm_cfg.VIRTUAL_HOST_OVERVIEW and (
113                    mlist.web_page_url.find('/%s/' % hostname) == -1 and
114                    mlist.web_page_url.find('/%s:' % hostname) == -1):
115                 # List is for different identity of this host - skip it.
116                 continue
117             else:
118                 advertised.append((mlist.GetScriptURL('listinfo'),
119                                    mlist.real_name,
120                                    Utils.websafe(mlist.description)))
121     if msg:
122         greeting = FontAttr(msg, color="ff5060", size="+1")
123     else:
124         greeting = FontAttr(_('Welcome!'), size='+2')
125
126     welcome = [greeting]
127     mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format()
128     if not advertised:
129         welcome.extend(
130             _('''<p>There currently are no publicly-advertised
131             %(mailmanlink)s mailing lists on %(hostname)s.'''))
132     else:
133         welcome.append(
134             _('''<p>Below is a listing of all the public mailing lists on
135             %(hostname)s.  Click on a list name to get more information about
136             the list, or to subscribe, unsubscribe, and change the preferences
137             on your subscription.'''))
138
139     # set up some local variables
140     adj = msg and _('right') or ''
141     siteowner = Utils.get_site_email()
142     welcome.extend(
143         (_(''' To visit the general information page for an unadvertised list,
144         open a URL similar to this one, but with a '/' and the %(adj)s
145         list name appended.
146         <p>List administrators, you can visit '''),
147          Link(Utils.ScriptURL('admin'),
148               _('the list admin overview page')),
149          _(''' to find the management interface for your list.
150          <p>If you are having trouble using the lists, please contact '''),
151          Link('mailto:' + siteowner, siteowner),
152          '.<p>'))
153
154     table.AddRow([apply(Container, welcome)])
155     table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2)
156
157     if advertised:
158         table.AddRow(['&nbsp;', '&nbsp;'])
159         table.AddRow([Bold(FontAttr(_('List'), size='+2')),
160                       Bold(FontAttr(_('Description'), size='+2'))
161                       ])
162         highlight = 1
163         for url, real_name, description in advertised:
164             table.AddRow(
165                 [Link(url, Bold(real_name)),
166                       description or Italic(_('[no description available]'))])
167             if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR:
168                 table.AddRowInfo(table.GetCurrentRowIndex(),
169                                  bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR)
170             highlight = not highlight
171
172     doc.AddItem(table)
173     doc.AddItem('<hr>')
174     doc.AddItem(MailmanLogo())
175     print doc.Format()
176
177
178
179
180 def list_listinfo(mlist, lang):
181     # Generate list specific listinfo
182     doc = HeadlessDocument()
183     doc.set_language(lang)
184
185     replacements = mlist.GetStandardReplacements(lang)
186
187     if not mlist.digestable or not mlist.nondigestable:
188         replacements['<mm-digest-radio-button>'] = ""
189         replacements['<mm-undigest-radio-button>'] = ""
190         replacements['<mm-digest-question-start>'] = '<!-- '
191         replacements['<mm-digest-question-end>'] = ' -->'
192     else:
193         replacements['<mm-digest-radio-button>'] = mlist.FormatDigestButton()
194         replacements['<mm-undigest-radio-button>'] = \
195                                                    mlist.FormatUndigestButton()
196         replacements['<mm-digest-question-start>'] = ''
197         replacements['<mm-digest-question-end>'] = ''
198     replacements['<mm-plain-digests-button>'] = \
199                                               mlist.FormatPlainDigestsButton()
200     replacements['<mm-mime-digests-button>'] = mlist.FormatMimeDigestsButton()
201     replacements['<mm-subscribe-box>'] = mlist.FormatBox('email', size=30)
202     replacements['<mm-subscribe-button>'] = mlist.FormatButton(
203         'email-button', text=_('Subscribe'))
204     replacements['<mm-new-password-box>'] = mlist.FormatSecureBox('pw')
205     replacements['<mm-confirm-password>'] = mlist.FormatSecureBox('pw-conf')
206     replacements['<mm-subscribe-form-start>'] = mlist.FormatFormStart(
207         'subscribe')
208     if mm_cfg.SUBSCRIBE_FORM_SECRET:
209         now = str(int(time.time()))
210         remote = os.environ.get('HTTP_FORWARDED_FOR',
211                  os.environ.get('HTTP_X_FORWARDED_FOR',
212                  os.environ.get('REMOTE_ADDR',
213                                 'w.x.y.z')))
214         # Try to accept a range in case of load balancers, etc.  (LP: #1447445)
215         if remote.find('.') >= 0:
216             # ipv4 - drop last octet
217             remote = remote.rsplit('.', 1)[0]
218         else:
219             # ipv6 - drop last 16 (could end with :: in which case we just
220             #        drop one : resulting in an invalid format, but it's only
221             #        for our hash so it doesn't matter.
222             remote = remote.rsplit(':', 1)[0]
223         # get CAPTCHA data
224         (captcha_question, captcha_box, captcha_idx) = Captcha.displayhtml(mlist, mm_cfg.CAPTCHAS)
225         replacements['<mm-captcha-question>'] = captcha_question
226         replacements['<mm-captcha-box>'] = captcha_box
227         # fill form
228         replacements['<mm-subscribe-form-start>'] += (
229                 '<input type="hidden" name="sub_form_token" value="%s:%s:%s">\n'
230                 % (now, captcha_idx, Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET +
231                           now +
232                           captcha_idx +
233                           mlist.internal_name() +
234                           remote
235                           ).hexdigest()
236                     )
237                 )
238     # Roster form substitutions
239     replacements['<mm-roster-form-start>'] = mlist.FormatFormStart('roster')
240     replacements['<mm-roster-option>'] = mlist.FormatRosterOptionForUser(lang)
241     # Options form substitutions
242     replacements['<mm-options-form-start>'] = mlist.FormatFormStart('options')
243     replacements['<mm-editing-options>'] = mlist.FormatEditingOption(lang)
244     replacements['<mm-info-button>'] = SubmitButton('UserOptions',
245                                                     _('Edit Options')).Format()
246     # If only one language is enabled for this mailing list, omit the choice
247     # buttons.
248     if len(mlist.GetAvailableLanguages()) == 1:
249         displang = ''
250     else:
251         displang = mlist.FormatButton('displang-button',
252                                       text = _("View this page in"))
253     replacements['<mm-displang-box>'] = displang
254     replacements['<mm-lang-form-start>'] = mlist.FormatFormStart('listinfo')
255     replacements['<mm-fullname-box>'] = mlist.FormatBox('fullname', size=30)
256
257     # Do the expansion.
258     doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang))
259     print doc.Format()
260
261
262
263
264 if __name__ == "__main__":
265     main()