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([' ', ' ']) 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> </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