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