Index: head/www/moinmoin/Makefile =================================================================== --- head/www/moinmoin/Makefile +++ head/www/moinmoin/Makefile @@ -3,19 +3,22 @@ PORTNAME= moinmoin PORTVERSION= 1.9.9 +PORTREVISION= 1 CATEGORIES= www python -MASTER_SITES= http://static.moinmo.in/files/ +MASTER_SITES= https://static.moinmo.in/files/ DISTNAME= moin-${PORTVERSION} MAINTAINER= python@FreeBSD.org -COMMENT= Python clone of WikiWiki +COMMENT= Easy to use, full-featured and extensible wiki software package LICENSE= GPLv2 LICENSE_FILE= ${WRKSRC}/docs/licenses/COPYING USES= cpe python:2.7 shebangfix -CPE_VENDOR= moinmo USE_PYTHON= distutils + +NO_ARCH= yes +CPE_VENDOR= moinmo PYDISTUTILS_PKGNAME= moin SHEBANG_FILES= wiki/server/moin* @@ -24,6 +27,16 @@ SUB_FILES= pkg-install SUB_LIST= MOINDIR=${MOINDIR} MOINDEST=${MOINDEST} MOINVER=${MOINVER} \ HTDOCS=${PYTHON_SITELIBDIR}/MoinMoin/web/static/htdocs + +OPTIONS_DEFINE= RECAPTCHA VERIFYEMAIL +OPTIONS_DEFAULT= RECAPTCHA VERIFYEMAIL +OPTIONS_SUB= yes + +RECAPTCHA_DESC= reCAPTCHA v2 support +VERIFYEMAIL_DESC= Support for verifying new account email addresses + +RECAPTCHA_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-recaptchav2 +VERIFYEMAIL_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-verifyemail CGIUSER?= www CGIGROUP?= www Index: head/www/moinmoin/files/extra-patch-recaptchav2 =================================================================== --- head/www/moinmoin/files/extra-patch-recaptchav2 +++ head/www/moinmoin/files/extra-patch-recaptchav2 @@ -0,0 +1,299 @@ +# Based on https://gist.github.com/tfoote/675b98df53369e199dea + +--- MoinMoin/action/AttachFile.py.orig 2016-10-31 20:44:01 UTC ++++ MoinMoin/action/AttachFile.py +@@ -44,6 +44,7 @@ from MoinMoin import config, packages + from MoinMoin.Page import Page + from MoinMoin.util import filesys, timefuncs + from MoinMoin.security.textcha import TextCha ++from MoinMoin.security.sec_recaptcha import ReCaptcha + from MoinMoin.events import FileAttachedEvent, FileRemovedEvent, send_event + + action_name = __name__.split('.')[-1] +@@ -654,6 +655,7 @@ def send_uploadform(pagename, request): +
+ + %(textcha)s ++%(recaptcha)s +

+ + +@@ -671,6 +673,7 @@ def send_uploadform(pagename, request): + 'overwrite_checked': ('', 'checked')[request.form.get('overwrite', '0') == '1'], + 'upload_button': _('Upload'), + 'textcha': TextCha(request).render(), ++ 'recaptcha': ReCaptcha(request).render(), + 'ticket': wikiutil.createTicket(request), + }) + +@@ -728,6 +731,8 @@ def _do_upload(pagename, request): + # but it could be extended to more/all attachment write access + if not TextCha(request).check_answer_from_form(): + return _('TextCha: Wrong answer! Go back and try again...') ++ if not ReCaptcha(request).check_answer_from_form(): ++ return _('ReCaptcha: Wrong answer! Go back and try again...') + + form = request.form + +--- MoinMoin/action/CopyPage.py.orig 2016-10-31 20:44:01 UTC ++++ MoinMoin/action/CopyPage.py +@@ -14,6 +14,7 @@ from MoinMoin.Page import Page + from MoinMoin.PageEditor import PageEditor + from MoinMoin.action import ActionBase + from MoinMoin.security.textcha import TextCha ++from MoinMoin.security.sec_recaptcha import ReCaptcha + + class CopyPage(ActionBase): + """ Copy page action +@@ -45,11 +46,14 @@ class CopyPage(ActionBase): + + def do_action(self): + """ copy this page to "pagename" """ ++ status = False + _ = self._ + # Currently we only check TextCha for upload (this is what spammers ususally do), + # but it could be extended to more/all attachment write access + if not TextCha(self.request).check_answer_from_form(): + return False, _('TextCha: Wrong answer! Go back and try again...') ++ if not ReCaptcha(self.request).check_answer_from_form(): ++ return status, _('ReCaptcha: Wrong answer! Go back and try again...') + + form = self.form + newpagename = form.get('newpagename', u'') +@@ -90,6 +94,7 @@ class CopyPage(ActionBase): + + d = { + 'textcha': TextCha(self.request).render(), ++ 'recaptcha': ReCaptcha(self.request).render(), + 'subpage': subpages, + 'subpages_checked': ('', 'checked')[self.request.args.get('subpages_checked', '0') == '1'], + 'subpage_label': _('Copy all /subpages too?'), +@@ -105,6 +110,7 @@ class CopyPage(ActionBase): +
+
+ %(textcha)s ++%(recaptcha)s + + +
+@@ -140,6 +146,7 @@ class CopyPage(ActionBase): + else: + d = { + 'textcha': TextCha(self.request).render(), ++ 'recaptcha': ReCaptcha(self.request).render(), + 'pagename': wikiutil.escape(self.pagename, True), + 'newname_label': _("New name"), + 'comment_label': _("Optional reason for the copying"), +@@ -147,6 +154,7 @@ class CopyPage(ActionBase): + } + return ''' + %(textcha)s ++%(recaptcha)s +
+ + +--- MoinMoin/action/edit.py.orig 2016-10-31 20:44:01 UTC ++++ MoinMoin/action/edit.py +@@ -163,6 +163,9 @@ def execute(pagename, request): + from MoinMoin.security.textcha import TextCha + if not TextCha(request).check_answer_from_form(): + raise pg.SaveError(_('TextCha: Wrong answer! Try again below...')) ++ from MoinMoin.security.sec_recaptcha import ReCaptcha ++ if not ReCaptcha(request).check_answer_from_form(): ++ raise pg.SaveError(_('ReCaptcha: Wrong answer! Try again below...')) + if request.cfg.comment_required and not comment: + raise pg.SaveError(_('Supplying a comment is mandatory. Write a comment below and try again...')) + savemsg = pg.saveText(savetext, rev, trivial=trivial, comment=comment) +--- MoinMoin/action/Load.py.orig 2016-10-31 20:44:01 UTC ++++ MoinMoin/action/Load.py +@@ -14,6 +14,7 @@ from MoinMoin.action import ActionBase, AttachFile + from MoinMoin.PageEditor import PageEditor + from MoinMoin.Page import Page + from MoinMoin.security.textcha import TextCha ++from MoinMoin.security.sec_recaptcha import ReCaptcha + + class Load(ActionBase): + """ Load page action +@@ -40,6 +41,8 @@ class Load(ActionBase): + # but it could be extended to more/all attachment write access + if not TextCha(request).check_answer_from_form(): + return status, _('TextCha: Wrong answer! Go back and try again...') ++ if not ReCaptcha(request).check_answer_from_form(): ++ return _('ReCaptcha: Wrong answer! Go back and try again...') + + comment = form.get('comment', u'') + comment = wikiutil.clean_input(comment) +@@ -97,6 +100,7 @@ class Load(ActionBase): +
+ + %(textcha)s ++%(recaptcha)s +

+ + +@@ -115,6 +119,7 @@ class Load(ActionBase): + 'buttons_html': buttons_html, + 'action_name': self.form_trigger, + 'textcha': TextCha(self.request).render(), ++ 'recaptcha': ReCaptcha(self.request).render(), + } + + def execute(pagename, request): +--- MoinMoin/action/newaccount.py.orig 2016-10-31 20:44:01 UTC ++++ MoinMoin/action/newaccount.py +@@ -10,6 +10,7 @@ from MoinMoin import user, wikiutil + from MoinMoin.Page import Page + from MoinMoin.widget import html + from MoinMoin.security.textcha import TextCha ++from MoinMoin.security.sec_recaptcha import ReCaptcha + from MoinMoin.auth import MoinAuth + + +@@ -26,6 +27,9 @@ def _create_user(request): + if not TextCha(request).check_answer_from_form(): + return _('TextCha: Wrong answer! Go back and try again...') + ++ if not ReCaptcha(request).check_answer_from_form(): ++ return _('ReCaptcha: Wrong answer! Go back and try again...') ++ + # Create user profile + theuser = user.User(request, auth_method="new-user") + +@@ -141,6 +145,17 @@ def _create_form(request): + if textcha: + td.append(textcha.render()) + row.append(td) ++ ++ recaptcha = ReCaptcha(request) ++ if recaptcha.is_enabled(): ++ row = html.TR() ++ tbl.append(row) ++ row.append(html.TD().append(html.STRONG().append( ++ html.Text(_('ReCaptcha (required)'))))) ++ td = html.TD() ++ if recaptcha: ++ td.append(recaptcha.render()) ++ row.append(td) + + row = html.TR() + tbl.append(row) +--- MoinMoin/PageEditor.py.orig 2016-10-31 20:44:01 UTC ++++ MoinMoin/PageEditor.py +@@ -422,6 +422,9 @@ If you don't want that, hit '''%(cancel_button_text)s' + from MoinMoin.security.textcha import TextCha + request.write(TextCha(request).render()) + ++ from MoinMoin.security.sec_recaptcha import ReCaptcha ++ request.write(ReCaptcha(request).render()) ++ + # Add textarea with page text + self.sendconfirmleaving() + +--- MoinMoin/PageGraphicalEditor.py.orig 2016-10-31 20:44:01 UTC ++++ MoinMoin/PageGraphicalEditor.py +@@ -305,6 +305,9 @@ If you don't want that, hit '''%(cancel_button_text)s' + from MoinMoin.security.textcha import TextCha + request.write(TextCha(request).render()) + ++ from MoinMoin.security.sec_recaptcha import ReCaptcha ++ request.write(ReCaptcha(request).render()) ++ + self.sendconfirmleaving() # TODO update state of flgChange to make this work, see PageEditor + + # Add textarea with page text +--- MoinMoin/security/sec_recaptcha.py.orig 2018-05-02 03:24:23 UTC ++++ MoinMoin/security/sec_recaptcha.py +@@ -0,0 +1,93 @@ ++# -*- coding: iso-8859-1 -*- ++""" ++ MoinMoin - recaptcha support ++ ++ Based heavily on the textcha support in textcha.py ++ ++ @copyright: 2011 by Steve McIntyre ++ @copyright: 2018 by d42 ++ @license: GNU GPL, see COPYING for details. ++""" ++import json ++import urllib ++import urllib2 ++from textwrap import dedent ++ ++from MoinMoin import log ++ ++logging = log.getLogger(__name__) ++ ++ ++class ReCaptcha(object): ++ """ Recaptcha support """ ++ ++ VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify" ++ ++ def __init__(self, request): ++ """ Initialize the Recaptcha setup. ++ ++ @param request: the request object ++ """ ++ self.request = request ++ self.user_info = request.user.valid and request.user.name or request.remote_addr ++ ++ self.site_key = getattr(request.cfg, "recaptcha_site_key", None) ++ self.secret_key = getattr(request.cfg, "recaptcha_secret_key", None) ++ ++ def is_enabled(self): ++ """ check if we're configured, i.e. we have a key ++ """ ++ return self.site_key and self.secret_key ++ ++ def check_answer_from_form(self, form=None): ++ form = self.request.form if form is None else form ++ ++ if not self.is_enabled(): ++ return True ++ ++ return self._submit( ++ response=form.get("g-recaptcha-response"), ++ remoteip=self.request.remote_addr ++ ) ++ ++ def _submit(self, response, remoteip): ++ ++ def encode_if_necessary(s): ++ return s.encode("utf-8") if isinstance(s, unicode) else s ++ ++ data = urllib.urlencode({ ++ "secret": encode_if_necessary(self.secret_key), ++ "response": encode_if_necessary(response), ++ "remoteip": encode_if_necessary(remoteip), ++ }) ++ ++ request = urllib2.Request( ++ url=self.VERIFY_URL, ++ data=data, ++ headers={"Content-type": "application/x-www-form-urlencoded"} ++ ) ++ ++ try: ++ resp = urllib2.urlopen(request) ++ http_code = resp.getcode() ++ resp_json = json.loads(resp.read()) ++ return resp_json["success"] if http_code == 200 else False ++ except urllib2.URLError as e: ++ logging.exception(e) ++ return False ++ finally: ++ resp.close() ++ ++ def render(self, form=None): ++ """ Checks if ReCaptchas are enabled and returns HTML for one, ++ or an empty string if they are not enabled. ++ ++ @return: unicode result html ++ """ ++ if not self.is_enabled(): ++ return u"" ++ ++ return dedent(u""" ++ ++

++ """.format(SITE_KEY=self.site_key)) Index: head/www/moinmoin/files/extra-patch-verifyemail =================================================================== --- head/www/moinmoin/files/extra-patch-verifyemail +++ head/www/moinmoin/files/extra-patch-verifyemail @@ -0,0 +1,232 @@ +Description: Add support for requiring new accounts to be verified by email +Origin: http://moinmo.in/MoinMoinPatch/VerifyAccountCreationByEmail +Author: Steve McIntyre +Last-Update: 2013-09-04 + +--- ./MoinMoin/action/newaccount.py 2014-10-17 12:45:32.000000000 -0700 ++++ ./MoinMoin/action/newaccount.py 2016-01-15 13:53:33.209638000 -0800 +@@ -12,5 +12,27 @@ + from MoinMoin.security.textcha import TextCha + from MoinMoin.auth import MoinAuth ++from MoinMoin.mail import sendmail ++import subprocess + ++def _send_verification_mail(request, user): ++ _ = request.getText ++ querystr = {'action': 'verifyaccount', ++ 'i': user.id, ++ 'v': user.account_verification} ++ page = Page(request, "FrontPage") ++ pagelink = "%(link)s" % {'link': request.getQualifiedURL(page.url(request, querystr))} ++ subject = _('[%(sitename)s] account verification check for new user %(username)s') % { ++ 'sitename': request.page.cfg.sitename or request.url_root, ++ 'username': user.name, ++ } ++ ++ text = "Please verify your account by visiting this URL:\n\n %(link)s\n\n" % { ++ 'link': pagelink} ++ ++ mailok, msg = sendmail.sendmail(request, user.email, subject, text, request.cfg.mail_from) ++ if mailok: ++ return (1, _("Verification message sent to %(email)s" % {'email': user.email})) ++ else: ++ return (mailok, msg) + + def _create_user(request): +@@ -43,6 +65,16 @@ + + # Name required to be unique. Check if name belong to another user. +- if user.getUserId(request, theuser.name): +- return _("This user name already belongs to somebody else.") ++ userid = user.getUserId(request, theuser.name) ++ if userid: ++ if request.cfg.require_email_verification and theuser.account_verification: ++ resendlink = request.page.url(request, querystr={ ++ 'action': 'newaccount', ++ 'i': userid, ++ 'resend': '1'}) ++ return _('This user name already belongs to somebody else. If this is a new account' ++ ' and you need another verification link, try ' ++ 'sending another one. ' % resendlink) ++ else: ++ return _("This user name already belongs to somebody else.") + + # try to get the password and pw repeat +@@ -73,16 +105,39 @@ + theuser.email = email.strip() + if not theuser.email and 'email' not in request.cfg.user_form_remove: +- return _("Please provide your email address. If you lose your" +- " login information, you can get it by email.") ++ if request.cfg.require_email_verification: ++ return _("Please provide your email address. You will need it" ++ " to be able to confirm your registration.") ++ else: ++ return _("Please provide your email address. If you lose your" ++ " login information, you can get it by email.") + + # Email should be unique - see also MoinMoin/script/accounts/moin_usercheck.py + if theuser.email and request.cfg.user_email_unique: +- if user.get_by_email_address(request, theuser.email): +- return _("This email already belongs to somebody else.") ++ emailuser = user.get_by_email_address(request, theuser.email) ++ if emailuser: ++ if request.cfg.require_email_verification and theuser.account_verification: ++ resendlink = request.page.url(request, querystr={ ++ 'action': 'newaccount', ++ 'i': emailuser.id, ++ 'resend': '1'}) ++ return _('This email already belongs to somebody else. If this is a new account' ++ ' and you need another verification link, try ' ++ 'sending another one. ' % resendlink) ++ else: ++ return _("This email already belongs to somebody else.") ++ ++ # Send verification links if desired ++ if request.cfg.require_email_verification: ++ mailok, msg = _send_verification_mail(request, theuser) ++ if mailok: ++ result = _("User account created! Use the link in your email (%s) to verify your account" ++ " then you will be able to use this account to login..." % theuser.email) ++ else: ++ request.theme.add_msg(_("Unable to send verification mail, %s. Account creation aborted." % msg), "error") ++ else: ++ result = _("User account created! You can use this account to login now...") + + # save data + theuser.save() +- +- result = _("User account created! You can use this account to login now...") + return result + +@@ -171,7 +226,18 @@ + submitted = form.has_key('create') + ++ uid = request.values.get('i', None) ++ resend = request.values.get('resend', None) ++ + if submitted: # user pressed create button + request.theme.add_msg(_create_user(request), "dialog") + return page.send_page() ++ if resend and uid: ++ theuser = user.User(request, id=uid) ++ mailok, msg = _send_verification_mail(request, theuser) ++ if mailok: ++ request.theme.add_msg(_("Verification message re-sent to %s" % theuser.email), "dialog") ++ else: ++ request.theme.add_msg(_("Unable to re-send verification message, %s" % msg), "dialog") ++ return page.send_page() + else: # show create form + request.theme.send_title(_("Create Account"), pagename=pagename) +--- ./MoinMoin/action/verifyaccount.py 1969-12-31 16:00:00.000000000 -0800 ++++ ./MoinMoin/action/verifyaccount.py 2016-01-15 13:53:33.209957000 -0800 +@@ -0,0 +1,64 @@ ++# -*- coding: iso-8859-1 -*- ++""" ++ MoinMoin - verify account action ++ ++ @copyright: 2012 Steve McIntyre ++ @license: GNU GPL, see COPYING for details. ++""" ++ ++from MoinMoin import user, wikiutil ++from MoinMoin.Page import Page ++from MoinMoin.widget import html ++from MoinMoin.auth import MoinAuth ++ ++def execute(pagename, request): ++ found = False ++ for auth in request.cfg.auth: ++ if isinstance(auth, MoinAuth): ++ found = True ++ break ++ ++ if not found: ++ # we will not have linked, so forbid access ++ request.makeForbidden(403, 'No MoinAuth in auth list') ++ return ++ ++ page = Page(request, "FrontPage") ++ _ = request.getText ++ ++ if not request.cfg.require_email_verification: ++ result = _("Verification not configured!") ++ request.theme.add_msg(result, "error") ++ return page.send_page() ++ ++ uid = request.values.get('i', None) ++ verify = request.values.get('v', None) ++ ++ # Grab user profile ++ theuser = user.User(request, id=uid) ++ ++ # Compare the verification code ++ if not theuser.valid: ++ result = _("Unable to verify user account i=%s v=%s") % (uid, verify) ++ request.theme.add_msg(result, "error") ++ return page.send_page() ++ ++ if not theuser.account_verification: ++ result = _("User account has been verified!") ++ request.theme.add_msg(result, "error") ++ return page.send_page() ++ ++ if theuser.account_verification != verify: ++ result = _("Unable to verify user account i=%s v=%s") % (uid, verify) ++ request.theme.add_msg(result, "error") ++ return page.send_page() ++ ++ # All looks sane. Mark verification as done, save data ++ theuser.account_verification = "" ++ theuser.save() ++ ++ loginlink = request.page.url(request, querystr={'action': 'login'}) ++ result = _('User account verified! You can use this account to login now...' % loginlink) ++ request.theme.add_msg(result, "dialog") ++ return page.send_page() ++ +--- ./MoinMoin/auth/__init__.py 2014-10-17 12:45:32.000000000 -0700 ++++ ./MoinMoin/auth/__init__.py 2016-01-15 13:53:33.210285000 -0800 +@@ -250,6 +250,13 @@ + check_surge_protect(request, action='auth-name', username=username) + +- u = user.User(request, name=username, password=password, auth_method=self.name) ++ u = user.User(request, name=username, password=password, auth_method=self.name) + if u.valid: ++ try: ++ verification = u.account_verification ++ except: ++ verification = False ++ if request.cfg.require_email_verification and verification: ++ logging.debug("%s: could not authenticate user %r (not verified yet)" % (self.name, username)) ++ return ContinueLogin(user_obj, _("User account not verified yet.")) + logging.debug("%s: successfully authenticated user %r (valid)" % (self.name, u.name)) + log_attempt("auth/login (moin)", True, request, username) +--- ./MoinMoin/config/multiconfig.py 2014-10-17 12:45:32.000000000 -0700 ++++ ./MoinMoin/config/multiconfig.py 2016-01-15 13:53:33.210918000 -0800 +@@ -1097,4 +1097,6 @@ + ('userprefs_disabled', [], + "Disable the listed user preferences plugins."), ++ ('require_email_verification', False , ++ "Require verification of new user accounts."), + )), + # ========================================================================== +--- ./MoinMoin/user.py 2014-10-17 12:45:32.000000000 -0700 ++++ ./MoinMoin/user.py 2016-01-15 13:53:33.211435000 -0800 +@@ -24,4 +24,5 @@ + from copy import deepcopy + import md5crypt ++import uuid + + try: +@@ -523,4 +524,10 @@ + if password is not None: + self.enc_password = encodePassword(self._cfg, password) ++ self.account_creation_date = str(time.time()) ++ self.account_creation_host = self._request.remote_addr ++ if self._cfg.require_email_verification: ++ self.account_verification = uuid.uuid4() ++ else: ++ self.account_verification = "" + + # "may" so we can say "if user.may.read(pagename):" + Index: head/www/moinmoin/files/patch-setup.py =================================================================== --- head/www/moinmoin/files/patch-setup.py +++ head/www/moinmoin/files/patch-setup.py @@ -1,6 +1,6 @@ ---- ./setup.py.orig 2010-04-30 10:09:50.411058590 -0400 -+++ ./setup.py 2010-04-30 10:10:32.801862294 -0400 -@@ -26,6 +26,7 @@ +--- setup.py.orig 2016-10-31 20:44:02 UTC ++++ setup.py +@@ -26,6 +26,7 @@ def isbad(name): return (name.startswith('.') or name.startswith('#') or name.endswith('.pickle') or Index: head/www/moinmoin/pkg-descr =================================================================== --- head/www/moinmoin/pkg-descr +++ head/www/moinmoin/pkg-descr @@ -2,4 +2,4 @@ it's a discussion medium; it's a repository; it's a mail system; it's a tool for collaboration. -WWW: http://moinmo.in/ +WWW: https://moinmo.in/ Index: head/www/moinmoin/pkg-plist =================================================================== --- head/www/moinmoin/pkg-plist +++ head/www/moinmoin/pkg-plist @@ -170,6 +170,9 @@ %%PYTHON_SITELIBDIR%%/MoinMoin/action/userprofile.py %%PYTHON_SITELIBDIR%%/MoinMoin/action/userprofile.pyc %%PYTHON_SITELIBDIR%%/MoinMoin/action/userprofile.%%PYTHON_PYOEXTENSION%% +%%VERIFYEMAIL%%%%PYTHON_SITELIBDIR%%/MoinMoin/action/verifyaccount.py +%%VERIFYEMAIL%%%%PYTHON_SITELIBDIR%%/MoinMoin/action/verifyaccount.pyc +%%VERIFYEMAIL%%%%PYTHON_SITELIBDIR%%/MoinMoin/action/verifyaccount.%%PYTHON_PYOEXTENSION%% %%PYTHON_SITELIBDIR%%/MoinMoin/auth/_PHPsessionParser.py %%PYTHON_SITELIBDIR%%/MoinMoin/auth/_PHPsessionParser.pyc %%PYTHON_SITELIBDIR%%/MoinMoin/auth/_PHPsessionParser.%%PYTHON_PYOEXTENSION%% @@ -1039,6 +1042,9 @@ %%PYTHON_SITELIBDIR%%/MoinMoin/security/textcha.py %%PYTHON_SITELIBDIR%%/MoinMoin/security/textcha.pyc %%PYTHON_SITELIBDIR%%/MoinMoin/security/textcha.%%PYTHON_PYOEXTENSION%% +%%RECAPTCHA%%%%PYTHON_SITELIBDIR%%/MoinMoin/security/sec_recaptcha.py +%%RECAPTCHA%%%%PYTHON_SITELIBDIR%%/MoinMoin/security/sec_recaptcha.pyc +%%RECAPTCHA%%%%PYTHON_SITELIBDIR%%/MoinMoin/security/sec_recaptcha.%%PYTHON_PYOEXTENSION%% %%PYTHON_SITELIBDIR%%/MoinMoin/stats/__init__.py %%PYTHON_SITELIBDIR%%/MoinMoin/stats/__init__.pyc %%PYTHON_SITELIBDIR%%/MoinMoin/stats/__init__.%%PYTHON_PYOEXTENSION%%