Merge lp:mailman/2.1 into lp:mailman

Proposed by Salah hazaa
Status: Rejected
Rejected by: Mark Sapiro
Proposed branch: lp:mailman/2.1
Merge into: lp:mailman
Diff against target: 724340 lines (+712277/-0) (has conflicts)
2354 files modified
.bzrignore (+26/-0)
ACKNOWLEDGMENTS (+251/-0)
BUGS (+13/-0)
FAQ (+383/-0)
INSTALL (+25/-0)
Mailman/Archiver/Archiver.py (+244/-0)
Mailman/Archiver/HyperArch.py (+1335/-0)
Mailman/Archiver/HyperDatabase.py (+344/-0)
Mailman/Archiver/Makefile.in (+73/-0)
Mailman/Archiver/__init__.py (+17/-0)
Mailman/Archiver/pipermail.py (+908/-0)
Mailman/Autoresponder.py (+43/-0)
Mailman/Bouncer.py (+354/-0)
Mailman/Bouncers/AOL.py (+45/-0)
Mailman/Bouncers/BouncerAPI.py (+70/-0)
Mailman/Bouncers/Caiwireless.py (+45/-0)
Mailman/Bouncers/Compuserve.py (+45/-0)
Mailman/Bouncers/DSN.py (+88/-0)
Mailman/Bouncers/Exchange.py (+47/-0)
Mailman/Bouncers/Exim.py (+30/-0)
Mailman/Bouncers/GroupWise.py (+72/-0)
Mailman/Bouncers/LLNL.py (+31/-0)
Mailman/Bouncers/Makefile.in (+75/-0)
Mailman/Bouncers/Microsoft.py (+53/-0)
Mailman/Bouncers/Netscape.py (+88/-0)
Mailman/Bouncers/Postfix.py (+85/-0)
Mailman/Bouncers/Qmail.py (+74/-0)
Mailman/Bouncers/SMTP32.py (+60/-0)
Mailman/Bouncers/SimpleMatch.py (+241/-0)
Mailman/Bouncers/SimpleWarning.py (+92/-0)
Mailman/Bouncers/Sina.py (+47/-0)
Mailman/Bouncers/Yahoo.py (+68/-0)
Mailman/Bouncers/Yale.py (+79/-0)
Mailman/Bouncers/__init__.py (+15/-0)
Mailman/CSRFcheck.py (+103/-0)
Mailman/Cgi/Auth.py (+61/-0)
Mailman/Cgi/Makefile.in (+72/-0)
Mailman/Cgi/__init__.py (+15/-0)
Mailman/Cgi/admin.py (+1821/-0)
Mailman/Cgi/admindb.py (+979/-0)
Mailman/Cgi/confirm.py (+870/-0)
Mailman/Cgi/create.py (+461/-0)
Mailman/Cgi/edithtml.py (+273/-0)
Mailman/Cgi/listinfo.py (+285/-0)
Mailman/Cgi/options.py (+1170/-0)
Mailman/Cgi/private.py (+227/-0)
Mailman/Cgi/rmlist.py (+262/-0)
Mailman/Cgi/roster.py (+155/-0)
Mailman/Cgi/subscribe.py (+361/-0)
Mailman/Commands/Makefile.in (+70/-0)
Mailman/Commands/__init__.py (+15/-0)
Mailman/Commands/cmd_confirm.py (+112/-0)
Mailman/Commands/cmd_echo.py (+26/-0)
Mailman/Commands/cmd_end.py (+33/-0)
Mailman/Commands/cmd_help.py (+92/-0)
Mailman/Commands/cmd_info.py (+49/-0)
Mailman/Commands/cmd_join.py (+20/-0)
Mailman/Commands/cmd_leave.py (+20/-0)
Mailman/Commands/cmd_lists.py (+69/-0)
Mailman/Commands/cmd_password.py (+122/-0)
Mailman/Commands/cmd_remove.py (+20/-0)
Mailman/Commands/cmd_set.py (+359/-0)
Mailman/Commands/cmd_stop.py (+20/-0)
Mailman/Commands/cmd_subscribe.py (+151/-0)
Mailman/Commands/cmd_unsubscribe.py (+92/-0)
Mailman/Commands/cmd_who.py (+152/-0)
Mailman/Defaults.py.in (+1846/-0)
Mailman/Deliverer.py (+233/-0)
Mailman/Digester.py (+73/-0)
Mailman/Errors.py (+169/-0)
Mailman/GatewayManager.py (+38/-0)
Mailman/Gui/Archive.py (+44/-0)
Mailman/Gui/Autoresponse.py (+98/-0)
Mailman/Gui/Bounce.py (+204/-0)
Mailman/Gui/ContentFilter.py (+199/-0)
Mailman/Gui/Digest.py (+160/-0)
Mailman/Gui/GUIBase.py (+225/-0)
Mailman/Gui/General.py (+600/-0)
Mailman/Gui/Language.py (+122/-0)
Mailman/Gui/Makefile.in (+70/-0)
Mailman/Gui/Membership.py (+36/-0)
Mailman/Gui/NonDigest.py (+194/-0)
Mailman/Gui/Passwords.py (+31/-0)
Mailman/Gui/Privacy.py (+734/-0)
Mailman/Gui/Topics.py (+169/-0)
Mailman/Gui/Usenet.py (+139/-0)
Mailman/Gui/__init__.py (+32/-0)
Mailman/HTMLFormatter.py (+451/-0)
Mailman/Handlers/Acknowledge.py (+62/-0)
Mailman/Handlers/AfterDelivery.py (+28/-0)
Mailman/Handlers/Approve.py (+166/-0)
Mailman/Handlers/AvoidDuplicates.py (+110/-0)
Mailman/Handlers/CalcRecips.py (+235/-0)
Mailman/Handlers/Cleanse.py (+98/-0)
Mailman/Handlers/CleanseDKIM.py (+56/-0)
Mailman/Handlers/CookHeaders.py (+515/-0)
Mailman/Handlers/Decorate.py (+250/-0)
Mailman/Handlers/Emergency.py (+37/-0)
Mailman/Handlers/FileRecips.py (+49/-0)
Mailman/Handlers/Hold.py (+301/-0)
Mailman/Handlers/Makefile.in (+70/-0)
Mailman/Handlers/MimeDel.py (+296/-0)
Mailman/Handlers/Moderate.py (+172/-0)
Mailman/Handlers/OwnerRecips.py (+27/-0)
Mailman/Handlers/Replybot.py (+121/-0)
Mailman/Handlers/SMTPDirect.py (+450/-0)
Mailman/Handlers/Scrubber.py (+546/-0)
Mailman/Handlers/Sendmail.py (+116/-0)
Mailman/Handlers/SpamDetect.py (+207/-0)
Mailman/Handlers/Tagger.py (+170/-0)
Mailman/Handlers/ToArchive.py (+40/-0)
Mailman/Handlers/ToDigest.py (+429/-0)
Mailman/Handlers/ToOutgoing.py (+55/-0)
Mailman/Handlers/ToUsenet.py (+44/-0)
Mailman/Handlers/WrapMessage.py (+95/-0)
Mailman/Handlers/__init__.py (+15/-0)
Mailman/ListAdmin.py (+627/-0)
Mailman/LockFile.py (+603/-0)
Mailman/Logging/Logger.py (+104/-0)
Mailman/Logging/Makefile.in (+70/-0)
Mailman/Logging/MultiLogger.py (+76/-0)
Mailman/Logging/StampedLogger.py (+89/-0)
Mailman/Logging/Syslog.py (+80/-0)
Mailman/Logging/Utils.py (+52/-0)
Mailman/Logging/__init__.py (+15/-0)
Mailman/MTA/Makefile.in (+70/-0)
Mailman/MTA/Manual.py (+146/-0)
Mailman/MTA/Postfix.py (+496/-0)
Mailman/MTA/Utils.py (+84/-0)
Mailman/MTA/__init__.py (+15/-0)
Mailman/MailList.py (+1772/-0)
Mailman/Mailbox.py (+120/-0)
Mailman/Makefile.in (+100/-0)
Mailman/MemberAdaptor.py (+349/-0)
Mailman/Message.py (+344/-0)
Mailman/OldStyleMemberships.py (+370/-0)
Mailman/Pending.py (+192/-0)
Mailman/Post.py (+61/-0)
Mailman/Queue/ArchRunner.py (+80/-0)
Mailman/Queue/BounceRunner.py (+380/-0)
Mailman/Queue/CommandRunner.py (+299/-0)
Mailman/Queue/IncomingRunner.py (+194/-0)
Mailman/Queue/MaildirRunner.py (+196/-0)
Mailman/Queue/Makefile.in (+70/-0)
Mailman/Queue/NewsRunner.py (+182/-0)
Mailman/Queue/OutgoingRunner.py (+142/-0)
Mailman/Queue/RetryRunner.py (+49/-0)
Mailman/Queue/Runner.py (+276/-0)
Mailman/Queue/Switchboard.py (+255/-0)
Mailman/Queue/VirginRunner.py (+43/-0)
Mailman/Queue/__init__.py (+15/-0)
Mailman/Queue/sbcache.py (+26/-0)
Mailman/SafeDict.py (+70/-0)
Mailman/SecurityManager.py (+382/-0)
Mailman/Site.py (+113/-0)
Mailman/TopicMgr.py (+61/-0)
Mailman/UserDesc.py (+67/-0)
Mailman/Utils.py (+1612/-0)
Mailman/Version.py (+49/-0)
Mailman/__init__.py (+15/-0)
Mailman/htmlformat.py (+726/-0)
Mailman/i18n.py (+181/-0)
Mailman/mm_cfg.py.dist.in (+52/-0)
Mailman/versions.py (+642/-0)
Makefile.in (+151/-0)
NEWS (+4995/-0)
README (+180/-0)
README-I18N.en (+213/-0)
README.CONTRIB (+17/-0)
README.NETSCAPE (+57/-0)
README.USERAGENT (+49/-0)
STYLEGUIDE.txt (+162/-0)
TODO (+174/-0)
UPGRADING (+419/-0)
bin/Makefile.in (+80/-0)
bin/add_members (+315/-0)
bin/arch (+201/-0)
bin/b4b5-archfix (+96/-0)
bin/change_pw (+211/-0)
bin/check_db (+153/-0)
bin/check_perms (+404/-0)
bin/cleanarch (+172/-0)
bin/clone_member (+225/-0)
bin/config_list (+365/-0)
bin/convert.py (+44/-0)
bin/discard (+121/-0)
bin/dumpdb (+156/-0)
bin/export.py (+381/-0)
bin/find_member (+184/-0)
bin/fix_url.py (+97/-0)
bin/genaliases (+124/-0)
bin/inject (+108/-0)
bin/list_admins (+102/-0)
bin/list_lists (+136/-0)
bin/list_members (+311/-0)
bin/list_owners (+121/-0)
bin/mailman-config (+40/-0)
bin/mailmanctl (+556/-0)
bin/mmsitepass (+105/-0)
bin/msgfmt.py (+203/-0)
bin/newlist (+274/-0)
bin/pygettext.py (+545/-0)
bin/qrunner (+281/-0)
bin/rb-archfix (+108/-0)
bin/remove_members (+186/-0)
bin/reset_pw.py (+90/-0)
bin/rmlist (+162/-0)
bin/show_qfiles (+95/-0)
bin/sync_members (+297/-0)
bin/transcheck (+413/-0)
bin/unshunt (+94/-0)
bin/update (+820/-0)
bin/version (+26/-0)
bin/withlist (+299/-0)
configure (+5808/-0)
configure.in (+800/-0)
contrib/README (+4/-0)
contrib/README.check_perms_grsecurity (+14/-0)
contrib/README.courier_to_mailman (+14/-0)
contrib/README.import_majordomo_into_mailman (+60/-0)
contrib/README.mm-handler (+215/-0)
contrib/README.mm-handler-2.1.10 (+31/-0)
contrib/README.mmdsr (+45/-0)
contrib/README.post_count (+14/-0)
contrib/README.redhat_fhs.patch (+305/-0)
contrib/README.sitemap (+18/-0)
contrib/README.sitemapgen (+11/-0)
contrib/auto (+116/-0)
contrib/check_perms_grsecurity.py (+182/-0)
contrib/courier-to-mailman.py (+125/-0)
contrib/import_majordomo_into_mailman.pl (+1418/-0)
contrib/mailman.mc (+143/-0)
contrib/majordomo2mailman.pl (+692/-0)
contrib/mm-handler (+236/-0)
contrib/mm-handler-2.1.10 (+300/-0)
contrib/mmdsr (+691/-0)
contrib/post_count (+39/-0)
contrib/qmail-to-mailman.py (+122/-0)
contrib/redhat_fhs.patch (+251/-0)
contrib/rotatelogs.py (+103/-0)
contrib/sitemap (+86/-0)
contrib/sitemapgen (+164/-0)
contrib/virtusertable (+37/-0)
cron/Makefile.in (+76/-0)
cron/bumpdigests (+96/-0)
cron/checkdbs (+211/-0)
cron/crontab.in.in (+27/-0)
cron/cull_bad_shunt (+126/-0)
cron/disabled (+227/-0)
cron/gate_news (+293/-0)
cron/mailpasswds (+243/-0)
cron/nightly_gzip (+160/-0)
cron/senddigests (+120/-0)
doc/mailman-admin.ps (+4672/-0)
doc/mailman-admin.txt (+1392/-0)
doc/mailman-admin/about.html (+112/-0)
doc/mailman-admin/contents.html (+126/-0)
doc/mailman-admin/front.html (+114/-0)
doc/mailman-admin/general-personality.html (+221/-0)
doc/mailman-admin/index.html (+130/-0)
doc/mailman-admin/internals.pl (+26/-0)
doc/mailman-admin/intlabels.pl (+3/-0)
doc/mailman-admin/labels.pl (+41/-0)
doc/mailman-admin/mailman-admin.css (+243/-0)
doc/mailman-admin/mailman-admin.html (+130/-0)
doc/mailman-admin/node11.html (+200/-0)
doc/mailman-admin/node12.html (+100/-0)
doc/mailman-admin/node13.html (+184/-0)
doc/mailman-admin/node14.html (+207/-0)
doc/mailman-admin/node15.html (+120/-0)
doc/mailman-admin/node16.html (+156/-0)
doc/mailman-admin/node17.html (+108/-0)
doc/mailman-admin/node18.html (+318/-0)
doc/mailman-admin/node19.html (+228/-0)
doc/mailman-admin/node20.html (+179/-0)
doc/mailman-admin/node21.html (+204/-0)
doc/mailman-admin/node23.html (+133/-0)
doc/mailman-admin/node24.html (+131/-0)
doc/mailman-admin/node25.html (+196/-0)
doc/mailman-admin/node26.html (+137/-0)
doc/mailman-admin/node27.html (+101/-0)
doc/mailman-admin/node28.html (+94/-0)
doc/mailman-admin/node29.html (+94/-0)
doc/mailman-admin/node3.html (+129/-0)
doc/mailman-admin/node30.html (+95/-0)
doc/mailman-admin/node31.html (+94/-0)
doc/mailman-admin/node32.html (+94/-0)
doc/mailman-admin/node33.html (+94/-0)
doc/mailman-admin/node34.html (+98/-0)
doc/mailman-admin/node35.html (+113/-0)
doc/mailman-admin/node4.html (+160/-0)
doc/mailman-admin/node5.html (+126/-0)
doc/mailman-admin/node6.html (+135/-0)
doc/mailman-admin/node7.html (+147/-0)
doc/mailman-admin/node8.html (+177/-0)
doc/mailman-admin/node9.html (+117/-0)
doc/mailman-admin/sender-filters.html (+246/-0)
doc/mailman-install.ps (+5723/-0)
doc/mailman-install.txt (+1652/-0)
doc/mailman-install/about.html (+109/-0)
doc/mailman-install/bsd-issues.html (+114/-0)
doc/mailman-install/building.html (+106/-0)
doc/mailman-install/create-install-dir.html (+158/-0)
doc/mailman-install/customizing.html (+131/-0)
doc/mailman-install/exim3-transport.html (+113/-0)
doc/mailman-install/front.html (+182/-0)
doc/mailman-install/index.html (+135/-0)
doc/mailman-install/internals.pl (+58/-0)
doc/mailman-install/intlabels.pl (+3/-0)
doc/mailman-install/labels.pl (+109/-0)
doc/mailman-install/mail-server.html (+164/-0)
doc/mailman-install/mailman-install.css (+243/-0)
doc/mailman-install/mailman-install.html (+135/-0)
doc/mailman-install/node10.html (+212/-0)
doc/mailman-install/node12.html (+144/-0)
doc/mailman-install/node15.html (+111/-0)
doc/mailman-install/node16.html (+133/-0)
doc/mailman-install/node17.html (+152/-0)
doc/mailman-install/node18.html (+111/-0)
doc/mailman-install/node2.html (+130/-0)
doc/mailman-install/node20.html (+115/-0)
doc/mailman-install/node21.html (+113/-0)
doc/mailman-install/node22.html (+98/-0)
doc/mailman-install/node23.html (+102/-0)
doc/mailman-install/node24.html (+128/-0)
doc/mailman-install/node25.html (+135/-0)
doc/mailman-install/node26.html (+137/-0)
doc/mailman-install/node27.html (+110/-0)
doc/mailman-install/node28.html (+120/-0)
doc/mailman-install/node29.html (+124/-0)
doc/mailman-install/node3.html (+110/-0)
doc/mailman-install/node30.html (+98/-0)
doc/mailman-install/node31.html (+129/-0)
doc/mailman-install/node32.html (+148/-0)
doc/mailman-install/node33.html (+116/-0)
doc/mailman-install/node34.html (+101/-0)
doc/mailman-install/node36.html (+115/-0)
doc/mailman-install/node37.html (+102/-0)
doc/mailman-install/node38.html (+98/-0)
doc/mailman-install/node4.html (+130/-0)
doc/mailman-install/node41.html (+160/-0)
doc/mailman-install/node42.html (+158/-0)
doc/mailman-install/node43.html (+109/-0)
doc/mailman-install/node44.html (+128/-0)
doc/mailman-install/node45.html (+154/-0)
doc/mailman-install/node47.html (+113/-0)
doc/mailman-install/node48.html (+155/-0)
doc/mailman-install/node50.html (+237/-0)
doc/mailman-install/node7.html (+251/-0)
doc/mailman-install/node8.html (+96/-0)
doc/mailman-install/node9.html (+147/-0)
doc/mailman-install/postfix-integration.html (+209/-0)
doc/mailman-install/postfix-virtual.html (+267/-0)
doc/mailman-install/qmail-issues.html (+307/-0)
doc/mailman-install/site-list.html (+131/-0)
doc/mailman-install/troubleshooting.html (+260/-0)
doc/mailman-member-es.ps (+5060/-0)
doc/mailman-member-es.txt (+1492/-0)
doc/mailman-member-es/WARNINGS (+4/-0)
doc/mailman-member-es/about.html (+112/-0)
doc/mailman-member-es/contents.html (+161/-0)
doc/mailman-member-es/front.html (+110/-0)
doc/mailman-member-es/images.pl (+30/-0)
doc/mailman-member-es/index.html (+170/-0)
doc/mailman-member-es/internals.pl (+114/-0)
doc/mailman-member-es/intlabels.pl (+3/-0)
doc/mailman-member-es/labels.pl (+217/-0)
doc/mailman-member-es/mailman-member-es.css (+243/-0)
doc/mailman-member-es/mailman-member-es.html (+170/-0)
doc/mailman-member-es/node10.html (+181/-0)
doc/mailman-member-es/node11.html (+114/-0)
doc/mailman-member-es/node12.html (+111/-0)
doc/mailman-member-es/node13.html (+184/-0)
doc/mailman-member-es/node14.html (+178/-0)
doc/mailman-member-es/node15.html (+121/-0)
doc/mailman-member-es/node16.html (+158/-0)
doc/mailman-member-es/node17.html (+171/-0)
doc/mailman-member-es/node18.html (+136/-0)
doc/mailman-member-es/node19.html (+113/-0)
doc/mailman-member-es/node20.html (+160/-0)
doc/mailman-member-es/node21.html (+146/-0)
doc/mailman-member-es/node22.html (+127/-0)
doc/mailman-member-es/node23.html (+141/-0)
doc/mailman-member-es/node24.html (+145/-0)
doc/mailman-member-es/node25.html (+208/-0)
doc/mailman-member-es/node26.html (+107/-0)
doc/mailman-member-es/node27.html (+148/-0)
doc/mailman-member-es/node28.html (+169/-0)
doc/mailman-member-es/node29.html (+130/-0)
doc/mailman-member-es/node3.html (+138/-0)
doc/mailman-member-es/node30.html (+194/-0)
doc/mailman-member-es/node31.html (+129/-0)
doc/mailman-member-es/node32.html (+129/-0)
doc/mailman-member-es/node33.html (+111/-0)
doc/mailman-member-es/node34.html (+107/-0)
doc/mailman-member-es/node35.html (+118/-0)
doc/mailman-member-es/node36.html (+151/-0)
doc/mailman-member-es/node37.html (+142/-0)
doc/mailman-member-es/node38.html (+109/-0)
doc/mailman-member-es/node39.html (+116/-0)
doc/mailman-member-es/node4.html (+118/-0)
doc/mailman-member-es/node40.html (+209/-0)
doc/mailman-member-es/node41.html (+321/-0)
doc/mailman-member-es/node42.html (+267/-0)
doc/mailman-member-es/node5.html (+149/-0)
doc/mailman-member-es/node6.html (+109/-0)
doc/mailman-member-es/node7.html (+143/-0)
doc/mailman-member-es/node8.html (+122/-0)
doc/mailman-member-es/node9.html (+206/-0)
doc/mailman-member.ps (+4346/-0)
doc/mailman-member.txt (+1290/-0)
doc/mailman-member/about.html (+112/-0)
doc/mailman-member/contents.html (+159/-0)
doc/mailman-member/front.html (+110/-0)
doc/mailman-member/images.pl (+18/-0)
doc/mailman-member/index.html (+171/-0)
doc/mailman-member/internals.pl (+114/-0)
doc/mailman-member/intlabels.pl (+3/-0)
doc/mailman-member/labels.pl (+217/-0)
doc/mailman-member/mailman-member.css (+243/-0)
doc/mailman-member/mailman-member.html (+171/-0)
doc/mailman-member/node10.html (+183/-0)
doc/mailman-member/node11.html (+114/-0)
doc/mailman-member/node12.html (+111/-0)
doc/mailman-member/node13.html (+182/-0)
doc/mailman-member/node14.html (+175/-0)
doc/mailman-member/node15.html (+122/-0)
doc/mailman-member/node16.html (+155/-0)
doc/mailman-member/node17.html (+169/-0)
doc/mailman-member/node18.html (+134/-0)
doc/mailman-member/node19.html (+113/-0)
doc/mailman-member/node20.html (+150/-0)
doc/mailman-member/node21.html (+143/-0)
doc/mailman-member/node22.html (+119/-0)
doc/mailman-member/node23.html (+141/-0)
doc/mailman-member/node24.html (+147/-0)
doc/mailman-member/node25.html (+189/-0)
doc/mailman-member/node26.html (+107/-0)
doc/mailman-member/node27.html (+144/-0)
doc/mailman-member/node28.html (+161/-0)
doc/mailman-member/node29.html (+132/-0)
doc/mailman-member/node3.html (+139/-0)
doc/mailman-member/node30.html (+185/-0)
doc/mailman-member/node31.html (+127/-0)
doc/mailman-member/node32.html (+124/-0)
doc/mailman-member/node33.html (+111/-0)
doc/mailman-member/node34.html (+108/-0)
doc/mailman-member/node35.html (+119/-0)
doc/mailman-member/node36.html (+143/-0)
doc/mailman-member/node37.html (+141/-0)
doc/mailman-member/node38.html (+109/-0)
doc/mailman-member/node39.html (+112/-0)
doc/mailman-member/node4.html (+113/-0)
doc/mailman-member/node40.html (+198/-0)
doc/mailman-member/node41.html (+320/-0)
doc/mailman-member/node42.html (+266/-0)
doc/mailman-member/node5.html (+142/-0)
doc/mailman-member/node6.html (+108/-0)
doc/mailman-member/node7.html (+141/-0)
doc/mailman-member/node8.html (+121/-0)
doc/mailman-member/node9.html (+195/-0)
gnu-COPYING-GPL (+340/-0)
install-sh (+250/-0)
messages/Makefile.in (+151/-0)
messages/ar/LC_MESSAGES/mailman.po (+14140/-0)
messages/ast/LC_MESSAGES/mailman.po (+14479/-0)
messages/ca/LC_MESSAGES/mailman.po (+13875/-0)
messages/cs/LC_MESSAGES/mailman.po (+11768/-0)
messages/da/LC_MESSAGES/mailman.po (+14470/-0)
messages/de/LC_MESSAGES/mailman.po (+14547/-0)
messages/de/README.de (+22/-0)
messages/docstring.files (+61/-0)
messages/el/LC_MESSAGES/mailman.po (+13532/-0)
messages/eo/LC_MESSAGES/mailman.po (+10551/-0)
messages/es/LC_MESSAGES/mailman.po (+14759/-0)
messages/es/README.es (+82/-0)
messages/et/LC_MESSAGES/mailman.po (+12847/-0)
messages/eu/LC_MESSAGES/mailman.po (+12728/-0)
messages/eu/README.eu (+103/-0)
messages/fa/LC_MESSAGES/mailman.po (+10393/-0)
messages/fi/LC_MESSAGES/mailman.po (+13703/-0)
messages/fi/README.fi (+13/-0)
messages/fr/LC_MESSAGES/mailman.po (+13772/-0)
messages/fr/README.fr (+7/-0)
messages/gl/LC_MESSAGES/README.gl (+3/-0)
messages/gl/LC_MESSAGES/mailman.po (+12735/-0)
messages/he/LC_MESSAGES/mailman.po (+12993/-0)
messages/hr/LC_MESSAGES/mailman.po (+12108/-0)
messages/hu/FAQ.hu (+464/-0)
messages/hu/INSTALL.hu (+640/-0)
messages/hu/LC_MESSAGES/mailman.po (+13280/-0)
messages/hu/README.BSD.hu (+28/-0)
messages/hu/README.CONTRIB.hu (+17/-0)
messages/hu/README.EXIM.hu (+359/-0)
messages/hu/README.LINUX.hu (+59/-0)
messages/hu/README.MACOSX.hu (+31/-0)
messages/hu/README.NETSCAPE.hu (+57/-0)
messages/hu/README.POSTFIX.hu (+239/-0)
messages/hu/README.QMAIL.hu (+186/-0)
messages/hu/README.SENDMAIL.hu (+86/-0)
messages/hu/README.USERAGENT.hu (+49/-0)
messages/hu/README.hu (+271/-0)
messages/hu/UPGRADING.hu (+391/-0)
messages/ia/LC_MESSAGES/mailman.po (+12773/-0)
messages/it/LC_MESSAGES/mailman.po (+14048/-0)
messages/it/README.it (+32/-0)
messages/ja/INSTALL (+27/-0)
messages/ja/LC_MESSAGES/mailman.po (+13027/-0)
messages/ja/README (+228/-0)
messages/ja/UPGRADING (+256/-0)
messages/ja/doc/Defaults.py.in (+2012/-0)
messages/ja/doc/mailman-install.tex (+1935/-0)
messages/ja/doc/mailman-member.tex (+1788/-0)
messages/ko/LC_MESSAGES/mailman.po (+11453/-0)
messages/ko/README.ko (+26/-0)
messages/lt/LC_MESSAGES/mailman.po (+10459/-0)
messages/mailman.pot (+9513/-0)
messages/marked.files (+133/-0)
messages/nl/LC_MESSAGES/mailman.po (+12142/-0)
messages/no/LC_MESSAGES/mailman.po (+13859/-0)
messages/pl/LC_MESSAGES/mailman.po (+11643/-0)
messages/pl/README.pl (+28/-0)
messages/pt/LC_MESSAGES/mailman.po (+13322/-0)
messages/pt_BR/LC_MESSAGES/mailman.po (+13750/-0)
messages/ro/LC_MESSAGES/mailman.po (+12954/-0)
messages/ru/LC_MESSAGES/mailman.po (+13663/-0)
messages/ru/README.ru (+17/-0)
messages/sk/LC_MESSAGES/mailman.po (+12135/-0)
messages/sk/README.sk (+11/-0)
messages/sl/LC_MESSAGES/mailman.po (+16158/-0)
messages/sr/LC_MESSAGES/mailman.po (+10203/-0)
messages/sr/README.sr (+6/-0)
messages/sv/LC_MESSAGES/mailman.po (+16631/-0)
messages/sv/README.sv (+30/-0)
messages/tr/LC_MESSAGES/mailman.po (+12023/-0)
messages/uk/LC_MESSAGES/mailman.po (+13273/-0)
messages/vi/LC_MESSAGES/mailman.po (+13317/-0)
messages/zh_CN/LC_MESSAGES/mailman.po (+12673/-0)
messages/zh_TW/LC_MESSAGES/mailman.po (+11844/-0)
misc/Makefile.in (+110/-0)
misc/mailman.in (+71/-0)
misc/paths.py.in (+99/-0)
misc/sitelist.cfg (+376/-0)
mkinstalldirs (+40/-0)
scripts/Makefile.in (+76/-0)
scripts/bounces (+61/-0)
scripts/confirm (+62/-0)
scripts/driver (+279/-0)
scripts/join (+61/-0)
scripts/leave (+61/-0)
scripts/owner (+67/-0)
scripts/post (+69/-0)
scripts/request (+64/-0)
src/Makefile.in (+136/-0)
src/cgi-wrapper.c (+68/-0)
src/common.c (+328/-0)
src/common.h (+61/-0)
src/mail-wrapper.c (+91/-0)
src/vsnprintf.c (+125/-0)
templates/Makefile.in (+77/-0)
templates/ar/admindbdetails.html (+62/-0)
templates/ar/admindbpreamble.html (+9/-0)
templates/ar/admindbsummary.html (+12/-0)
templates/ar/adminsubscribeack.txt (+4/-0)
templates/ar/adminunsubscribeack.txt (+3/-0)
templates/ar/admlogin.html (+39/-0)
templates/ar/approve.txt (+15/-0)
templates/ar/archidxentry.html (+4/-0)
templates/ar/archidxfoot.html (+21/-0)
templates/ar/archidxhead.html (+25/-0)
templates/ar/archlistend.html (+1/-0)
templates/ar/archliststart.html (+4/-0)
templates/ar/archtoc.html (+21/-0)
templates/ar/archtocentry.html (+12/-0)
templates/ar/archtocnombox.html (+19/-0)
templates/ar/article.html (+55/-0)
templates/ar/bounce.txt (+13/-0)
templates/ar/checkdbs.txt (+6/-0)
templates/ar/convert.txt (+34/-0)
templates/ar/cronpass.txt (+18/-0)
templates/ar/disabled.txt (+21/-0)
templates/ar/emptyarchive.html (+16/-0)
templates/ar/headfoot.html (+23/-0)
templates/ar/help.txt (+33/-0)
templates/ar/invite.txt (+20/-0)
templates/ar/listinfo.html (+144/-0)
templates/ar/masthead.txt (+13/-0)
templates/ar/newlist.txt (+34/-0)
templates/ar/nomoretoday.txt (+7/-0)
templates/ar/options.html (+299/-0)
templates/ar/postack.txt (+8/-0)
templates/ar/postauth.txt (+12/-0)
templates/ar/postheld.txt (+14/-0)
templates/ar/private.html (+56/-0)
templates/ar/probe.txt (+24/-0)
templates/ar/refuse.txt (+13/-0)
templates/ar/roster.html (+52/-0)
templates/ar/subauth.txt (+10/-0)
templates/ar/subscribe.html (+12/-0)
templates/ar/subscribeack.txt (+32/-0)
templates/ar/unsub.txt (+22/-0)
templates/ar/unsubauth.txt (+10/-0)
templates/ar/userpass.txt (+24/-0)
templates/ar/verify.txt (+22/-0)
templates/ast/admindbdetails.html (+25/-0)
templates/ast/admindbpreamble.html (+6/-0)
templates/ast/admindbsummary.html (+6/-0)
templates/ast/adminsubscribeack.txt (+4/-0)
templates/ast/adminunsubscribeack.txt (+4/-0)
templates/ast/admlogin.html (+34/-0)
templates/ast/approve.txt (+13/-0)
templates/ast/archidxentry.html (+4/-0)
templates/ast/archidxfoot.html (+21/-0)
templates/ast/archidxhead.html (+24/-0)
templates/ast/archlistend.html (+1/-0)
templates/ast/archliststart.html (+4/-0)
templates/ast/archtoc.html (+19/-0)
templates/ast/archtocentry.html (+11/-0)
templates/ast/archtocnombox.html (+18/-0)
templates/ast/article.html (+50/-0)
templates/ast/bounce.txt (+12/-0)
templates/ast/checkdbs.txt (+9/-0)
templates/ast/convert.txt (+33/-0)
templates/ast/cronpass.txt (+21/-0)
templates/ast/disabled.txt (+19/-0)
templates/ast/emptyarchive.html (+13/-0)
templates/ast/headfoot.html (+20/-0)
templates/ast/help.txt (+37/-0)
templates/ast/invite.txt (+19/-0)
templates/ast/listinfo.html (+136/-0)
templates/ast/masthead.txt (+15/-0)
templates/ast/newlist.txt (+37/-0)
templates/ast/nomoretoday.txt (+6/-0)
templates/ast/options.html (+266/-0)
templates/ast/postack.txt (+8/-0)
templates/ast/postauth.txt (+12/-0)
templates/ast/postheld.txt (+15/-0)
templates/ast/private.html (+50/-0)
templates/ast/probe.txt (+20/-0)
templates/ast/refuse.txt (+11/-0)
templates/ast/roster.html (+51/-0)
templates/ast/subauth.txt (+11/-0)
templates/ast/subscribe.html (+9/-0)
templates/ast/subscribeack.txt (+34/-0)
templates/ast/unsub.txt (+22/-0)
templates/ast/unsubauth.txt (+11/-0)
templates/ast/userpass.txt (+27/-0)
templates/ast/verify.txt (+17/-0)
templates/ca/admindbdetails.html (+63/-0)
templates/ca/admindbpreamble.html (+11/-0)
templates/ca/admindbsummary.html (+13/-0)
templates/ca/adminsubscribeack.txt (+4/-0)
templates/ca/adminunsubscribeack.txt (+3/-0)
templates/ca/admlogin.html (+40/-0)
templates/ca/approve.txt (+16/-0)
templates/ca/archidxentry.html (+4/-0)
templates/ca/archidxfoot.html (+21/-0)
templates/ca/archidxhead.html (+24/-0)
templates/ca/archlistend.html (+1/-0)
templates/ca/archliststart.html (+4/-0)
templates/ca/archtoc.html (+20/-0)
templates/ca/archtocentry.html (+12/-0)
templates/ca/archtocnombox.html (+18/-0)
templates/ca/article.html (+55/-0)
templates/ca/bounce.txt (+14/-0)
templates/ca/checkdbs.txt (+8/-0)
templates/ca/convert.txt (+36/-0)
templates/ca/cronpass.txt (+22/-0)
templates/ca/disabled.txt (+26/-0)
templates/ca/emptyarchive.html (+15/-0)
templates/ca/headfoot.html (+27/-0)
templates/ca/help.txt (+36/-0)
templates/ca/invite.txt (+19/-0)
templates/ca/listinfo.html (+147/-0)
templates/ca/masthead.txt (+16/-0)
templates/ca/newlist.txt (+41/-0)
templates/ca/nomoretoday.txt (+10/-0)
templates/ca/options.html (+319/-0)
templates/ca/postack.txt (+7/-0)
templates/ca/postauth.txt (+13/-0)
templates/ca/postheld.txt (+16/-0)
templates/ca/private.html (+60/-0)
templates/ca/probe.txt (+30/-0)
templates/ca/refuse.txt (+13/-0)
templates/ca/roster.html (+53/-0)
templates/ca/subauth.txt (+11/-0)
templates/ca/subscribe.html (+9/-0)
templates/ca/subscribeack.txt (+37/-0)
templates/ca/unsub.txt (+26/-0)
templates/ca/unsubauth.txt (+11/-0)
templates/ca/userpass.txt (+25/-0)
templates/ca/verify.txt (+26/-0)
templates/cs/admindbdetails.html (+66/-0)
templates/cs/admindbpreamble.html (+14/-0)
templates/cs/admindbsummary.html (+18/-0)
templates/cs/adminsubscribeack.txt (+4/-0)
templates/cs/adminunsubscribeack.txt (+3/-0)
templates/cs/admlogin.html (+37/-0)
templates/cs/approve.txt (+15/-0)
templates/cs/archidxentry.html (+4/-0)
templates/cs/archidxfoot.html (+21/-0)
templates/cs/archidxhead.html (+24/-0)
templates/cs/archlistend.html (+2/-0)
templates/cs/archliststart.html (+4/-0)
templates/cs/archtoc.html (+20/-0)
templates/cs/archtocentry.html (+13/-0)
templates/cs/archtocnombox.html (+18/-0)
templates/cs/article.html (+53/-0)
templates/cs/bounce.txt (+14/-0)
templates/cs/checkdbs.txt (+7/-0)
templates/cs/convert.txt (+42/-0)
templates/cs/cronpass.txt (+21/-0)
templates/cs/disabled.txt (+24/-0)
templates/cs/emptyarchive.html (+15/-0)
templates/cs/headfoot.html (+30/-0)
templates/cs/help.txt (+41/-0)
templates/cs/invite.txt (+22/-0)
templates/cs/listinfo.html (+145/-0)
templates/cs/masthead.txt (+15/-0)
templates/cs/newlist.txt (+39/-0)
templates/cs/nomoretoday.txt (+11/-0)
templates/cs/options.html (+295/-0)
templates/cs/postack.txt (+8/-0)
templates/cs/postauth.txt (+13/-0)
templates/cs/postheld.txt (+16/-0)
templates/cs/private.html (+58/-0)
templates/cs/probe.txt (+26/-0)
templates/cs/refuse.txt (+12/-0)
templates/cs/roster.html (+54/-0)
templates/cs/subauth.txt (+10/-0)
templates/cs/subscribe.html (+9/-0)
templates/cs/subscribeack.txt (+34/-0)
templates/cs/unsub.txt (+19/-0)
templates/cs/unsubauth.txt (+12/-0)
templates/cs/userpass.txt (+18/-0)
templates/cs/verify.txt (+26/-0)
templates/da/admindbdetails.html (+62/-0)
templates/da/admindbpreamble.html (+8/-0)
templates/da/admindbsummary.html (+10/-0)
templates/da/adminsubscribeack.txt (+2/-0)
templates/da/adminunsubscribeack.txt (+3/-0)
templates/da/admlogin.html (+39/-0)
templates/da/approve.txt (+15/-0)
templates/da/archidxfoot.html (+21/-0)
templates/da/archidxhead.html (+24/-0)
templates/da/archliststart.html (+4/-0)
templates/da/archtoc.html (+20/-0)
templates/da/archtocentry.html (+12/-0)
templates/da/archtocnombox.html (+18/-0)
templates/da/article.html (+54/-0)
templates/da/bounce.txt (+13/-0)
templates/da/checkdbs.txt (+7/-0)
templates/da/convert.txt (+39/-0)
templates/da/cronpass.txt (+22/-0)
templates/da/disabled.txt (+26/-0)
templates/da/emptyarchive.html (+14/-0)
templates/da/headfoot.html (+26/-0)
templates/da/help.txt (+35/-0)
templates/da/invite.txt (+22/-0)
templates/da/listinfo.html (+141/-0)
templates/da/masthead.txt (+17/-0)
templates/da/newlist.txt (+41/-0)
templates/da/nomoretoday.txt (+9/-0)
templates/da/options.html (+299/-0)
templates/da/postack.txt (+8/-0)
templates/da/postauth.txt (+15/-0)
templates/da/postheld.txt (+16/-0)
templates/da/private.html (+59/-0)
templates/da/probe.txt (+26/-0)
templates/da/refuse.txt (+13/-0)
templates/da/roster.html (+52/-0)
templates/da/subauth.txt (+12/-0)
templates/da/subscribe.html (+9/-0)
templates/da/subscribeack.txt (+42/-0)
templates/da/unsub.txt (+27/-0)
templates/da/unsubauth.txt (+11/-0)
templates/da/userpass.txt (+26/-0)
templates/da/verify.txt (+26/-0)
templates/de/adminaddrchgack.txt (+2/-0)
templates/de/admindbdetails.html (+73/-0)
templates/de/admindbpreamble.html (+10/-0)
templates/de/admindbsummary.html (+15/-0)
templates/de/adminsubscribeack.txt (+2/-0)
templates/de/adminunsubscribeack.txt (+3/-0)
templates/de/admlogin.html (+40/-0)
templates/de/approve.txt (+15/-0)
templates/de/archidxentry.html (+4/-0)
templates/de/archidxfoot.html (+21/-0)
templates/de/archidxhead.html (+24/-0)
templates/de/archlistend.html (+1/-0)
templates/de/archliststart.html (+4/-0)
templates/de/archtoc.html (+20/-0)
templates/de/archtocentry.html (+12/-0)
templates/de/archtocnombox.html (+18/-0)
templates/de/article.html (+54/-0)
templates/de/bounce.txt (+12/-0)
templates/de/checkdbs.txt (+9/-0)
templates/de/convert.txt (+34/-0)
templates/de/cronpass.txt (+18/-0)
templates/de/disabled.txt (+30/-0)
templates/de/emptyarchive.html (+13/-0)
templates/de/headfoot.html (+30/-0)
templates/de/help.txt (+39/-0)
templates/de/invite.txt (+21/-0)
templates/de/listinfo.html (+156/-0)
templates/de/masthead.txt (+22/-0)
templates/de/newlist.txt (+35/-0)
templates/de/nomoretoday.txt (+8/-0)
templates/de/options.html (+312/-0)
templates/de/postack.txt (+8/-0)
templates/de/postauth.txt (+13/-0)
templates/de/postheld.txt (+19/-0)
templates/de/private.html (+63/-0)
templates/de/probe.txt (+20/-0)
templates/de/refuse.txt (+11/-0)
templates/de/roster.html (+50/-0)
templates/de/subauth.txt (+10/-0)
templates/de/subscribe.html (+9/-0)
templates/de/subscribeack.txt (+38/-0)
templates/de/unsub.txt (+26/-0)
templates/de/unsubauth.txt (+8/-0)
templates/de/userpass.txt (+24/-0)
templates/de/verify.txt (+25/-0)
templates/el/admindbdetails.html (+62/-0)
templates/el/admindbpreamble.html (+10/-0)
templates/el/admindbsummary.html (+12/-0)
templates/el/adminsubscribeack.txt (+4/-0)
templates/el/adminunsubscribeack.txt (+3/-0)
templates/el/admlogin.html (+40/-0)
templates/el/approve.txt (+16/-0)
templates/el/archidxentry.html (+4/-0)
templates/el/archidxfoot.html (+21/-0)
templates/el/archidxhead.html (+24/-0)
templates/el/archlistend.html (+1/-0)
templates/el/archliststart.html (+4/-0)
templates/el/archtoc.html (+20/-0)
templates/el/archtocentry.html (+12/-0)
templates/el/archtocnombox.html (+19/-0)
templates/el/article.html (+55/-0)
templates/el/bounce.txt (+15/-0)
templates/el/checkdbs.txt (+7/-0)
templates/el/convert.txt (+41/-0)
templates/el/cronpass.txt (+24/-0)
templates/el/disabled.txt (+23/-0)
templates/el/emptyarchive.html (+15/-0)
templates/el/headfoot.html (+28/-0)
templates/el/help.txt (+32/-0)
templates/el/invite.txt (+21/-0)
templates/el/listinfo.html (+149/-0)
templates/el/masthead.txt (+16/-0)
templates/el/newlist.txt (+42/-0)
templates/el/nomoretoday.txt (+9/-0)
templates/el/options.html (+327/-0)
templates/el/postack.txt (+8/-0)
templates/el/postauth.txt (+13/-0)
templates/el/postheld.txt (+15/-0)
templates/el/private.html (+59/-0)
templates/el/probe.txt (+23/-0)
templates/el/refuse.txt (+13/-0)
templates/el/roster.html (+53/-0)
templates/el/subauth.txt (+11/-0)
templates/el/subscribe.html (+9/-0)
templates/el/subscribeack.txt (+41/-0)
templates/el/unsub.txt (+25/-0)
templates/el/unsubauth.txt (+11/-0)
templates/el/userpass.txt (+26/-0)
templates/el/verify.txt (+25/-0)
templates/en/adminaddrchgack.txt (+4/-0)
templates/en/admindbdetails.html (+60/-0)
templates/en/admindbpreamble.html (+10/-0)
templates/en/admindbsummary.html (+14/-0)
templates/en/adminsubscribeack.txt (+3/-0)
templates/en/adminunsubscribeack.txt (+3/-0)
templates/en/admlogin.html (+40/-0)
templates/en/approve.txt (+15/-0)
templates/en/archidxentry.html (+4/-0)
templates/en/archidxfoot.html (+21/-0)
templates/en/archidxhead.html (+24/-0)
templates/en/archlistend.html (+1/-0)
templates/en/archliststart.html (+4/-0)
templates/en/archtoc.html (+20/-0)
templates/en/archtocentry.html (+12/-0)
templates/en/archtocnombox.html (+18/-0)
templates/en/article.html (+55/-0)
templates/en/bounce.txt (+13/-0)
templates/en/checkdbs.txt (+7/-0)
templates/en/convert.txt (+34/-0)
templates/en/cronpass.txt (+19/-0)
templates/en/disabled.txt (+25/-0)
templates/en/emptyarchive.html (+15/-0)
templates/en/headfoot.html (+28/-0)
templates/en/help.txt (+33/-0)
templates/en/invite.txt (+20/-0)
templates/en/listinfo.html (+148/-0)
templates/en/masthead.txt (+13/-0)
templates/en/newlist.txt (+35/-0)
templates/en/nomoretoday.txt (+8/-0)
templates/en/options.html (+318/-0)
templates/en/postack.txt (+8/-0)
templates/en/postauth.txt (+13/-0)
templates/en/postheld.txt (+15/-0)
templates/en/private.html (+60/-0)
templates/en/probe.txt (+25/-0)
templates/en/refuse.txt (+13/-0)
templates/en/roster.html (+53/-0)
templates/en/subauth.txt (+11/-0)
templates/en/subscribe.html (+9/-0)
templates/en/subscribeack.txt (+33/-0)
templates/en/unsub.txt (+23/-0)
templates/en/unsubauth.txt (+11/-0)
templates/en/userpass.txt (+24/-0)
templates/en/verify.txt (+22/-0)
templates/eo/admlogin.html (+31/-0)
templates/eo/approve.txt (+14/-0)
templates/eo/archidxentry.html (+4/-0)
templates/eo/archidxfoot.html (+20/-0)
templates/eo/archidxhead.html (+24/-0)
templates/eo/archlistend.html (+1/-0)
templates/eo/archliststart.html (+4/-0)
templates/eo/archtoc.html (+20/-0)
templates/eo/archtocentry.html (+12/-0)
templates/eo/archtocnombox.html (+18/-0)
templates/eo/article.html (+55/-0)
templates/eo/bounce.txt (+15/-0)
templates/eo/cronpass.txt (+19/-0)
templates/eo/disabled.txt (+26/-0)
templates/eo/emptyarchive.html (+13/-0)
templates/eo/help.txt (+28/-0)
templates/eo/invite.txt (+21/-0)
templates/eo/listinfo.html (+138/-0)
templates/eo/masthead.txt (+13/-0)
templates/eo/nomoretoday.txt (+9/-0)
templates/eo/options.html (+256/-0)
templates/eo/postack.txt (+8/-0)
templates/eo/postauth.txt (+13/-0)
templates/eo/postheld.txt (+15/-0)
templates/eo/private.html (+49/-0)
templates/eo/probe.txt (+25/-0)
templates/eo/refuse.txt (+12/-0)
templates/eo/roster.html (+49/-0)
templates/eo/subauth.txt (+11/-0)
templates/eo/subscribe.html (+9/-0)
templates/eo/subscribeack.txt (+36/-0)
templates/eo/unsub.txt (+24/-0)
templates/eo/unsubauth.txt (+11/-0)
templates/eo/userpass.txt (+25/-0)
templates/eo/verify.txt (+24/-0)
templates/es/admindbdetails.html (+63/-0)
templates/es/admindbpreamble.html (+10/-0)
templates/es/admindbsummary.html (+13/-0)
templates/es/adminsubscribeack.txt (+4/-0)
templates/es/adminunsubscribeack.txt (+4/-0)
templates/es/admlogin.html (+38/-0)
templates/es/approve.txt (+16/-0)
templates/es/archidxentry.html (+4/-0)
templates/es/archidxfoot.html (+20/-0)
templates/es/archidxhead.html (+24/-0)
templates/es/archlistend.html (+1/-0)
templates/es/archliststart.html (+4/-0)
templates/es/archtoc.html (+20/-0)
templates/es/archtocentry.html (+12/-0)
templates/es/archtocnombox.html (+18/-0)
templates/es/article.html (+54/-0)
templates/es/bounce.txt (+12/-0)
templates/es/checkdbs.txt (+9/-0)
templates/es/convert.txt (+37/-0)
templates/es/cronpass.txt (+23/-0)
templates/es/disabled.txt (+27/-0)
templates/es/emptyarchive.html (+15/-0)
templates/es/handle_opts.html (+9/-0)
templates/es/headfoot.html (+32/-0)
templates/es/help.txt (+37/-0)
templates/es/invite.txt (+21/-0)
templates/es/listinfo.html (+150/-0)
templates/es/masthead.txt (+18/-0)
templates/es/newlist.txt (+37/-0)
templates/es/nomoretoday.txt (+10/-0)
templates/es/options.html (+328/-0)
templates/es/postack.txt (+7/-0)
templates/es/postauth.txt (+13/-0)
templates/es/postheld.txt (+16/-0)
templates/es/private.html (+54/-0)
templates/es/probe.txt (+24/-0)
templates/es/refuse.txt (+12/-0)
templates/es/roster.html (+52/-0)
templates/es/subauth.txt (+11/-0)
templates/es/subscribe.html (+9/-0)
templates/es/subscribeack.txt (+37/-0)
templates/es/unsub.txt (+24/-0)
templates/es/unsubauth.txt (+11/-0)
templates/es/userpass.txt (+28/-0)
templates/es/verify.txt (+26/-0)
templates/et/admindbdetails.html (+63/-0)
templates/et/admindbpreamble.html (+10/-0)
templates/et/admindbsummary.html (+13/-0)
templates/et/adminsubscribeack.txt (+4/-0)
templates/et/adminunsubscribeack.txt (+3/-0)
templates/et/admlogin.html (+36/-0)
templates/et/approve.txt (+14/-0)
templates/et/article.html (+56/-0)
templates/et/bounce.txt (+13/-0)
templates/et/checkdbs.txt (+9/-0)
templates/et/convert.txt (+32/-0)
templates/et/cronpass.txt (+19/-0)
templates/et/disabled.txt (+26/-0)
templates/et/emptyarchive.html (+14/-0)
templates/et/headfoot.html (+24/-0)
templates/et/help.txt (+33/-0)
templates/et/invite.txt (+22/-0)
templates/et/listinfo.html (+140/-0)
templates/et/masthead.txt (+14/-0)
templates/et/newlist.txt (+36/-0)
templates/et/options.html (+301/-0)
templates/et/postack.txt (+8/-0)
templates/et/postauth.txt (+12/-0)
templates/et/postheld.txt (+15/-0)
templates/et/private.html (+58/-0)
templates/et/refuse.txt (+12/-0)
templates/et/roster.html (+53/-0)
templates/et/subauth.txt (+6/-0)
templates/et/subscribe.html (+9/-0)
templates/et/subscribeack.txt (+35/-0)
templates/et/unsub.txt (+29/-0)
templates/et/unsubauth.txt (+7/-0)
templates/et/userpass.txt (+27/-0)
templates/et/verify.txt (+30/-0)
templates/eu/admindbdetails.html (+64/-0)
templates/eu/admindbpreamble.html (+10/-0)
templates/eu/admindbsummary.html (+12/-0)
templates/eu/adminsubscribeack.txt (+3/-0)
templates/eu/adminunsubscribeack.txt (+3/-0)
templates/eu/admlogin.html (+39/-0)
templates/eu/approve.txt (+16/-0)
templates/eu/archidxentry.html (+4/-0)
templates/eu/archidxfoot.html (+21/-0)
templates/eu/archidxhead.html (+24/-0)
templates/eu/archlistend.html (+1/-0)
templates/eu/archliststart.html (+4/-0)
templates/eu/archtoc.html (+20/-0)
templates/eu/archtocentry.html (+13/-0)
templates/eu/article.html (+55/-0)
templates/eu/bounce.txt (+13/-0)
templates/eu/checkdbs.txt (+7/-0)
templates/eu/convert.txt (+34/-0)
templates/eu/cronpass.txt (+18/-0)
templates/eu/disabled.txt (+22/-0)
templates/eu/emptyarchive.html (+15/-0)
templates/eu/headfoot.html (+28/-0)
templates/eu/help.txt (+31/-0)
templates/eu/invite.txt (+14/-0)
templates/eu/listinfo.html (+146/-0)
templates/eu/masthead.txt (+15/-0)
templates/eu/newlist.txt (+33/-0)
templates/eu/nomoretoday.txt (+9/-0)
templates/eu/options.html (+303/-0)
templates/eu/postack.txt (+5/-0)
templates/eu/postauth.txt (+16/-0)
templates/eu/postheld.txt (+10/-0)
templates/eu/private.html (+59/-0)
templates/eu/refuse.txt (+11/-0)
templates/eu/roster.html (+53/-0)
templates/eu/subauth.txt (+10/-0)
templates/eu/subscribe.html (+9/-0)
templates/eu/subscribeack.txt (+34/-0)
templates/eu/unsub.txt (+20/-0)
templates/eu/unsubauth.txt (+11/-0)
templates/eu/userpass.txt (+22/-0)
templates/eu/verify.txt (+20/-0)
templates/fa/adminsubscribeack.txt (+4/-0)
templates/fa/adminunsubscribeack.txt (+4/-0)
templates/fa/admlogin.html (+40/-0)
templates/fa/approve.txt (+12/-0)
templates/fa/archidxfoot.html (+21/-0)
templates/fa/archidxhead.html (+24/-0)
templates/fa/archliststart.html (+4/-0)
templates/fa/archtoc.html (+20/-0)
templates/fa/archtocentry.html (+12/-0)
templates/fa/archtocnombox.html (+18/-0)
templates/fa/article.html (+55/-0)
templates/fa/bounce.txt (+12/-0)
templates/fa/checkdbs.txt (+6/-0)
templates/fa/convert.txt (+18/-0)
templates/fa/cronpass.txt (+8/-0)
templates/fa/disabled.txt (+14/-0)
templates/fa/emptyarchive.html (+13/-0)
templates/fa/help.txt (+26/-0)
templates/fa/invite.txt (+15/-0)
templates/fa/listinfo.html (+138/-0)
templates/fa/masthead.txt (+12/-0)
templates/fa/nomoretoday.txt (+11/-0)
templates/fa/options.html (+249/-0)
templates/fa/postack.txt (+8/-0)
templates/fa/postauth.txt (+14/-0)
templates/fa/postheld.txt (+13/-0)
templates/fa/private.html (+56/-0)
templates/fa/refuse.txt (+11/-0)
templates/fa/roster.html (+50/-0)
templates/fa/subauth.txt (+12/-0)
templates/fa/subscribe.html (+9/-0)
templates/fa/subscribeack.txt (+28/-0)
templates/fa/unsub.txt (+20/-0)
templates/fa/unsubauth.txt (+10/-0)
templates/fa/userpass.txt (+17/-0)
templates/fa/verify.txt (+14/-0)
templates/fi/admindbdetails.html (+62/-0)
templates/fi/admindbpreamble.html (+9/-0)
templates/fi/admindbsummary.html (+17/-0)
templates/fi/adminsubscribeack.txt (+4/-0)
templates/fi/adminunsubscribeack.txt (+3/-0)
templates/fi/admlogin.html (+42/-0)
templates/fi/approve.txt (+17/-0)
templates/fi/article.html (+58/-0)
templates/fi/bounce.txt (+13/-0)
templates/fi/checkdbs.txt (+6/-0)
templates/fi/convert.txt (+41/-0)
templates/fi/cronpass.txt (+19/-0)
templates/fi/disabled.txt (+24/-0)
templates/fi/headfoot.html (+34/-0)
templates/fi/help.txt (+35/-0)
templates/fi/listinfo.html (+153/-0)
templates/fi/masthead.txt (+18/-0)
templates/fi/newlist.txt (+42/-0)
templates/fi/options.html (+290/-0)
templates/fi/postack.txt (+8/-0)
templates/fi/postauth.txt (+14/-0)
templates/fi/postheld.txt (+16/-0)
templates/fi/private.html (+60/-0)
templates/fi/refuse.txt (+13/-0)
templates/fi/roster.html (+53/-0)
templates/fi/subauth.txt (+8/-0)
templates/fi/subscribe.html (+9/-0)
templates/fi/subscribeack.txt (+41/-0)
templates/fi/unsub.txt (+25/-0)
templates/fi/unsubauth.txt (+9/-0)
templates/fi/userpass.txt (+26/-0)
templates/fi/verify.txt (+26/-0)
templates/fr/admindbdetails.html (+63/-0)
templates/fr/admindbpreamble.html (+12/-0)
templates/fr/admindbsummary.html (+11/-0)
templates/fr/adminsubscribeack.txt (+4/-0)
templates/fr/adminunsubscribeack.txt (+3/-0)
templates/fr/admlogin.html (+41/-0)
templates/fr/approve.txt (+16/-0)
templates/fr/archidxentry.html (+4/-0)
templates/fr/archidxfoot.html (+20/-0)
templates/fr/archidxhead.html (+24/-0)
templates/fr/archlistend.html (+1/-0)
templates/fr/archliststart.html (+6/-0)
templates/fr/archtoc.html (+19/-0)
templates/fr/archtocentry.html (+11/-0)
templates/fr/archtocnombox.html (+18/-0)
templates/fr/article.html (+57/-0)
templates/fr/bounce.txt (+13/-0)
templates/fr/checkdbs.txt (+7/-0)
templates/fr/convert.txt (+37/-0)
templates/fr/cronpass.txt (+21/-0)
templates/fr/disabled.txt (+28/-0)
templates/fr/emptyarchive.html (+18/-0)
templates/fr/handle_opts.html (+9/-0)
templates/fr/headfoot.html (+30/-0)
templates/fr/help.txt (+34/-0)
templates/fr/invite.txt (+20/-0)
templates/fr/listinfo.html (+152/-0)
templates/fr/masthead.txt (+16/-0)
templates/fr/newlist.txt (+39/-0)
templates/fr/nomoretoday.txt (+10/-0)
templates/fr/options.html (+341/-0)
templates/fr/postack.txt (+8/-0)
templates/fr/postauth.txt (+13/-0)
templates/fr/postheld.txt (+16/-0)
templates/fr/private.html (+59/-0)
templates/fr/probe.txt (+23/-0)
templates/fr/refuse.txt (+13/-0)
templates/fr/roster.html (+52/-0)
templates/fr/subauth.txt (+10/-0)
templates/fr/subscribe.html (+9/-0)
templates/fr/subscribeack.txt (+36/-0)
templates/fr/unsub.txt (+22/-0)
templates/fr/unsubauth.txt (+11/-0)
templates/fr/userpass.txt (+24/-0)
templates/fr/verify.txt (+21/-0)
templates/gl/admindbdetails.html (+83/-0)
templates/gl/admindbpreamble.html (+11/-0)
templates/gl/admindbsummary.html (+20/-0)
templates/gl/adminsubscribeack.txt (+4/-0)
templates/gl/adminunsubscribeack.txt (+4/-0)
templates/gl/admlogin.html (+42/-0)
templates/gl/approve.txt (+17/-0)
templates/gl/archidxentry.html (+4/-0)
templates/gl/archidxfoot.html (+20/-0)
templates/gl/archidxhead.html (+24/-0)
templates/gl/archlistend.html (+1/-0)
templates/gl/archliststart.html (+4/-0)
templates/gl/archtoc.html (+20/-0)
templates/gl/archtocentry.html (+12/-0)
templates/gl/article.html (+53/-0)
templates/gl/bounce.txt (+12/-0)
templates/gl/checkdbs.txt (+8/-0)
templates/gl/convert.txt (+38/-0)
templates/gl/cronpass.txt (+23/-0)
templates/gl/disabled.txt (+28/-0)
templates/gl/emptyarchive.html (+15/-0)
templates/gl/handle_opts.html (+9/-0)
templates/gl/headfoot.html (+31/-0)
templates/gl/help.txt (+37/-0)
templates/gl/invite.txt (+20/-0)
templates/gl/listinfo.html (+149/-0)
templates/gl/masthead.txt (+18/-0)
templates/gl/newlist.txt (+37/-0)
templates/gl/nomoretoday.txt (+9/-0)
templates/gl/options.html (+377/-0)
templates/gl/postack.txt (+7/-0)
templates/gl/postauth.txt (+13/-0)
templates/gl/postheld.txt (+15/-0)
templates/gl/private.html (+60/-0)
templates/gl/refuse.txt (+12/-0)
templates/gl/roster.html (+55/-0)
templates/gl/subauth.txt (+10/-0)
templates/gl/subscribe.html (+9/-0)
templates/gl/subscribeack.txt (+36/-0)
templates/gl/unsub.txt (+23/-0)
templates/gl/unsubauth.txt (+11/-0)
templates/gl/userpass.txt (+28/-0)
templates/gl/verify.txt (+26/-0)
templates/he/admindbdetails.html (+51/-0)
templates/he/admindbpreamble.html (+7/-0)
templates/he/admindbsummary.html (+10/-0)
templates/he/adminsubscribeack.txt (+4/-0)
templates/he/adminunsubscribeack.txt (+3/-0)
templates/he/admlogin.html (+41/-0)
templates/he/approve.txt (+13/-0)
templates/he/archidxentry.html (+4/-0)
templates/he/archidxfoot.html (+21/-0)
templates/he/archidxhead.html (+24/-0)
templates/he/archlistend.html (+1/-0)
templates/he/archliststart.html (+4/-0)
templates/he/archtoc.html (+20/-0)
templates/he/archtocentry.html (+12/-0)
templates/he/archtocnombox.html (+18/-0)
templates/he/article.html (+54/-0)
templates/he/bounce.txt (+14/-0)
templates/he/checkdbs.txt (+8/-0)
templates/he/convert.txt (+29/-0)
templates/he/cronpass.txt (+17/-0)
templates/he/disabled.txt (+24/-0)
templates/he/emptyarchive.html (+14/-0)
templates/he/headfoot.html (+25/-0)
templates/he/help.txt (+30/-0)
templates/he/invite.txt (+17/-0)
templates/he/listinfo.html (+143/-0)
templates/he/masthead.txt (+12/-0)
templates/he/newlist.txt (+34/-0)
templates/he/nomoretoday.txt (+6/-0)
templates/he/options.html (+308/-0)
templates/he/postack.txt (+8/-0)
templates/he/postauth.txt (+14/-0)
templates/he/postheld.txt (+15/-0)
templates/he/private.html (+58/-0)
templates/he/probe.txt (+23/-0)
templates/he/refuse.txt (+13/-0)
templates/he/roster.html (+52/-0)
templates/he/subauth.txt (+11/-0)
templates/he/subscribe.html (+9/-0)
templates/he/subscribeack.txt (+32/-0)
templates/he/unsub.txt (+21/-0)
templates/he/unsubauth.txt (+10/-0)
templates/he/userpass.txt (+22/-0)
templates/he/verify.txt (+20/-0)
templates/hr/admindbdetails.html (+58/-0)
templates/hr/admindbpreamble.html (+10/-0)
templates/hr/admindbsummary.html (+10/-0)
templates/hr/adminsubscribeack.txt (+4/-0)
templates/hr/adminunsubscribeack.txt (+3/-0)
templates/hr/admlogin.html (+38/-0)
templates/hr/approve.txt (+15/-0)
templates/hr/archidxentry.html (+4/-0)
templates/hr/archidxfoot.html (+21/-0)
templates/hr/archidxhead.html (+24/-0)
templates/hr/archlistend.html (+1/-0)
templates/hr/archliststart.html (+4/-0)
templates/hr/archtoc.html (+20/-0)
templates/hr/archtocentry.html (+12/-0)
templates/hr/article.html (+55/-0)
templates/hr/bounce.txt (+13/-0)
templates/hr/checkdbs.txt (+7/-0)
templates/hr/convert.txt (+32/-0)
templates/hr/cronpass.txt (+19/-0)
templates/hr/disabled.txt (+26/-0)
templates/hr/emptyarchive.html (+15/-0)
templates/hr/headfoot.html (+24/-0)
templates/hr/help.txt (+31/-0)
templates/hr/invite.txt (+19/-0)
templates/hr/listinfo.html (+145/-0)
templates/hr/masthead.txt (+13/-0)
templates/hr/newlist.txt (+35/-0)
templates/hr/nomoretoday.txt (+8/-0)
templates/hr/options.html (+309/-0)
templates/hr/postack.txt (+8/-0)
templates/hr/postauth.txt (+13/-0)
templates/hr/postheld.txt (+15/-0)
templates/hr/private.html (+57/-0)
templates/hr/refuse.txt (+13/-0)
templates/hr/roster.html (+52/-0)
templates/hr/subauth.txt (+11/-0)
templates/hr/subscribe.html (+9/-0)
templates/hr/subscribeack.txt (+34/-0)
templates/hr/unsub.txt (+23/-0)
templates/hr/unsubauth.txt (+10/-0)
templates/hr/userpass.txt (+24/-0)
templates/hr/verify.txt (+23/-0)
templates/hu/admindbdetails.html (+68/-0)
templates/hu/admindbpreamble.html (+11/-0)
templates/hu/admindbsummary.html (+13/-0)
templates/hu/adminsubscribeack.txt (+2/-0)
templates/hu/adminunsubscribeack.txt (+2/-0)
templates/hu/admlogin.html (+33/-0)
templates/hu/approve.txt (+15/-0)
templates/hu/archidxentry.html (+4/-0)
templates/hu/archidxfoot.html (+19/-0)
templates/hu/archidxhead.html (+23/-0)
templates/hu/archlistend.html (+1/-0)
templates/hu/archliststart.html (+4/-0)
templates/hu/archtoc.html (+20/-0)
templates/hu/archtocentry.html (+12/-0)
templates/hu/article.html (+54/-0)
templates/hu/bounce.txt (+13/-0)
templates/hu/checkdbs.txt (+7/-0)
templates/hu/convert.txt (+31/-0)
templates/hu/cronpass.txt (+20/-0)
templates/hu/disabled.txt (+24/-0)
templates/hu/emptyarchive.html (+14/-0)
templates/hu/headfoot.html (+23/-0)
templates/hu/help.txt (+34/-0)
templates/hu/illik.html (+1542/-0)
templates/hu/invite.txt (+21/-0)
templates/hu/listinfo.html (+143/-0)
templates/hu/masthead.txt (+14/-0)
templates/hu/newlist.txt (+35/-0)
templates/hu/nomoretoday.txt (+8/-0)
templates/hu/options.html (+308/-0)
templates/hu/postack.txt (+8/-0)
templates/hu/postauth.txt (+13/-0)
templates/hu/postheld.txt (+15/-0)
templates/hu/private.html (+54/-0)
templates/hu/probe.txt (+30/-0)
templates/hu/refuse.txt (+13/-0)
templates/hu/roster.html (+51/-0)
templates/hu/subauth.txt (+11/-0)
templates/hu/subscribe.html (+10/-0)
templates/hu/subscribeack.txt (+34/-0)
templates/hu/unsub.txt (+24/-0)
templates/hu/unsubauth.txt (+11/-0)
templates/hu/userpass.txt (+23/-0)
templates/hu/verify.txt (+25/-0)
templates/ia/adminaddrchgack.txt (+4/-0)
templates/ia/admindbdetails.html (+63/-0)
templates/ia/admindbpreamble.html (+10/-0)
templates/ia/admindbsummary.html (+14/-0)
templates/ia/adminsubscribeack.txt (+2/-0)
templates/ia/adminunsubscribeack.txt (+2/-0)
templates/ia/admlogin.html (+40/-0)
templates/ia/approve.txt (+16/-0)
templates/ia/archidxentry.html (+4/-0)
templates/ia/archidxfoot.html (+21/-0)
templates/ia/archidxhead.html (+24/-0)
templates/ia/archlistend.html (+1/-0)
templates/ia/archliststart.html (+4/-0)
templates/ia/archtoc.html (+20/-0)
templates/ia/archtocentry.html (+12/-0)
templates/ia/archtocnombox.html (+18/-0)
templates/ia/article.html (+54/-0)
templates/ia/bounce.txt (+14/-0)
templates/ia/checkdbs.txt (+7/-0)
templates/ia/convert.txt (+36/-0)
templates/ia/cronpass.txt (+20/-0)
templates/ia/disabled.txt (+27/-0)
templates/ia/emptyarchive.html (+15/-0)
templates/ia/headfoot.html (+26/-0)
templates/ia/help.txt (+34/-0)
templates/ia/invite.txt (+21/-0)
templates/ia/listinfo.html (+136/-0)
templates/ia/masthead.txt (+13/-0)
templates/ia/newlist.txt (+37/-0)
templates/ia/nomoretoday.txt (+11/-0)
templates/ia/options.html (+312/-0)
templates/ia/postack.txt (+8/-0)
templates/ia/postauth.txt (+13/-0)
templates/ia/postheld.txt (+15/-0)
templates/ia/private.html (+60/-0)
templates/ia/probe.txt (+29/-0)
templates/ia/refuse.txt (+12/-0)
templates/ia/roster.html (+52/-0)
templates/ia/subauth.txt (+11/-0)
templates/ia/subscribe.html (+9/-0)
templates/ia/subscribeack.txt (+35/-0)
templates/ia/unsub.txt (+27/-0)
templates/ia/unsubauth.txt (+11/-0)
templates/ia/userpass.txt (+26/-0)
templates/ia/verify.txt (+26/-0)
templates/it/admindbdetails.html (+69/-0)
templates/it/admindbpreamble.html (+11/-0)
templates/it/admindbsummary.html (+14/-0)
templates/it/adminsubscribeack.txt (+2/-0)
templates/it/adminunsubscribeack.txt (+2/-0)
templates/it/admlogin.html (+40/-0)
templates/it/approve.txt (+16/-0)
templates/it/archidxentry.html (+4/-0)
templates/it/archidxfoot.html (+21/-0)
templates/it/archidxhead.html (+24/-0)
templates/it/archlistend.html (+1/-0)
templates/it/archliststart.html (+4/-0)
templates/it/archtoc.html (+20/-0)
templates/it/archtocentry.html (+12/-0)
templates/it/archtocnombox.html (+18/-0)
templates/it/article.html (+54/-0)
templates/it/bounce.txt (+15/-0)
templates/it/checkdbs.txt (+10/-0)
templates/it/convert.txt (+38/-0)
templates/it/cronpass.txt (+21/-0)
templates/it/disabled.txt (+26/-0)
templates/it/emptyarchive.html (+16/-0)
templates/it/headfoot.html (+30/-0)
templates/it/help.txt (+33/-0)
templates/it/invite.txt (+21/-0)
templates/it/listinfo.html (+156/-0)
templates/it/masthead.txt (+16/-0)
templates/it/newlist.txt (+39/-0)
templates/it/nomoretoday.txt (+9/-0)
templates/it/options.html (+319/-0)
templates/it/postack.txt (+8/-0)
templates/it/postauth.txt (+13/-0)
templates/it/postheld.txt (+18/-0)
templates/it/private.html (+60/-0)
templates/it/probe.txt (+26/-0)
templates/it/refuse.txt (+13/-0)
templates/it/roster.html (+52/-0)
templates/it/subauth.txt (+11/-0)
templates/it/subscribe.html (+8/-0)
templates/it/subscribeack.txt (+34/-0)
templates/it/unsub.txt (+24/-0)
templates/it/unsubauth.txt (+11/-0)
templates/it/userpass.txt (+26/-0)
templates/it/verify.txt (+24/-0)
templates/ja/adminaddrchgack.txt (+4/-0)
templates/ja/admindbdetails.html (+62/-0)
templates/ja/admindbpreamble.html (+10/-0)
templates/ja/admindbsummary.html (+12/-0)
templates/ja/adminsubscribeack.txt (+3/-0)
templates/ja/adminunsubscribeack.txt (+3/-0)
templates/ja/admlogin.html (+40/-0)
templates/ja/approve.txt (+17/-0)
templates/ja/archidxentry.html (+4/-0)
templates/ja/archidxfoot.html (+21/-0)
templates/ja/archidxhead.html (+24/-0)
templates/ja/archlistend.html (+1/-0)
templates/ja/archliststart.html (+4/-0)
templates/ja/archtoc.html (+20/-0)
templates/ja/archtocentry.html (+12/-0)
templates/ja/archtocnombox.html (+18/-0)
templates/ja/article.html (+55/-0)
templates/ja/bounce.txt (+12/-0)
templates/ja/checkdbs.txt (+7/-0)
templates/ja/convert.txt (+33/-0)
templates/ja/cronpass.txt (+16/-0)
templates/ja/disabled.txt (+24/-0)
templates/ja/emptyarchive.html (+16/-0)
templates/ja/headfoot.html (+28/-0)
templates/ja/help.txt (+33/-0)
templates/ja/invite.txt (+23/-0)
templates/ja/listinfo.html (+148/-0)
templates/ja/masthead.txt (+15/-0)
templates/ja/newlist.txt (+35/-0)
templates/ja/nomoretoday.txt (+7/-0)
templates/ja/options.html (+299/-0)
templates/ja/postack.txt (+7/-0)
templates/ja/postauth.txt (+10/-0)
templates/ja/postheld.txt (+14/-0)
templates/ja/private.html (+58/-0)
templates/ja/probe.txt (+23/-0)
templates/ja/refuse.txt (+12/-0)
templates/ja/roster.html (+55/-0)
templates/ja/subauth.txt (+9/-0)
templates/ja/subscribe.html (+9/-0)
templates/ja/subscribeack.txt (+32/-0)
templates/ja/unsub.txt (+23/-0)
templates/ja/unsubauth.txt (+9/-0)
templates/ja/userpass.txt (+19/-0)
templates/ja/verify.txt (+23/-0)
templates/ko/admindbdetails.html (+56/-0)
templates/ko/admindbpreamble.html (+9/-0)
templates/ko/admindbsummary.html (+11/-0)
templates/ko/adminsubscribeack.txt (+4/-0)
templates/ko/adminunsubscribeack.txt (+3/-0)
templates/ko/admlogin.html (+34/-0)
templates/ko/approve.txt (+15/-0)
templates/ko/article.html (+53/-0)
templates/ko/bounce.txt (+12/-0)
templates/ko/checkdbs.txt (+8/-0)
templates/ko/convert.txt (+37/-0)
templates/ko/cronpass.txt (+16/-0)
templates/ko/disabled.txt (+25/-0)
templates/ko/emptyarchive.html (+14/-0)
templates/ko/headfoot.html (+22/-0)
templates/ko/help.txt (+32/-0)
templates/ko/invite.txt (+20/-0)
templates/ko/listinfo.html (+145/-0)
templates/ko/masthead.txt (+18/-0)
templates/ko/newlist.txt (+35/-0)
templates/ko/options.html (+308/-0)
templates/ko/postack.txt (+7/-0)
templates/ko/postauth.txt (+12/-0)
templates/ko/postheld.txt (+15/-0)
templates/ko/private.html (+59/-0)
templates/ko/refuse.txt (+13/-0)
templates/ko/roster.html (+50/-0)
templates/ko/subauth.txt (+9/-0)
templates/ko/subscribe.html (+9/-0)
templates/ko/subscribeack.txt (+33/-0)
templates/ko/unsub.txt (+20/-0)
templates/ko/unsubauth.txt (+10/-0)
templates/ko/userpass.txt (+20/-0)
templates/ko/verify.txt (+23/-0)
templates/lt/admindbdetails.html (+65/-0)
templates/lt/admindbpreamble.html (+13/-0)
templates/lt/admindbsummary.html (+14/-0)
templates/lt/adminsubscribeack.txt (+4/-0)
templates/lt/adminunsubscribeack.txt (+2/-0)
templates/lt/admlogin.html (+38/-0)
templates/lt/approve.txt (+13/-0)
templates/lt/archidxentry.html (+4/-0)
templates/lt/archidxfoot.html (+20/-0)
templates/lt/archidxhead.html (+23/-0)
templates/lt/archlistend.html (+1/-0)
templates/lt/archliststart.html (+4/-0)
templates/lt/archtoc.html (+20/-0)
templates/lt/archtocentry.html (+12/-0)
templates/lt/article.html (+54/-0)
templates/lt/bounce.txt (+13/-0)
templates/lt/checkdbs.txt (+9/-0)
templates/lt/convert.txt (+34/-0)
templates/lt/cronpass.txt (+12/-0)
templates/lt/disabled.txt (+28/-0)
templates/lt/emptyarchive.html (+14/-0)
templates/lt/headfoot.html (+26/-0)
templates/lt/help.txt (+36/-0)
templates/lt/invite.txt (+21/-0)
templates/lt/listinfo.html (+147/-0)
templates/lt/masthead.txt (+16/-0)
templates/lt/newlist.txt (+35/-0)
templates/lt/nomoretoday.txt (+12/-0)
templates/lt/options.html (+278/-0)
templates/lt/postack.txt (+5/-0)
templates/lt/postauth.txt (+10/-0)
templates/lt/postheld.txt (+10/-0)
templates/lt/private.html (+59/-0)
templates/lt/refuse.txt (+12/-0)
templates/lt/roster.html (+53/-0)
templates/lt/subauth.txt (+8/-0)
templates/lt/subscribe.html (+9/-0)
templates/lt/subscribeack.txt (+34/-0)
templates/lt/unsub.txt (+26/-0)
templates/lt/unsubauth.txt (+8/-0)
templates/lt/userpass.txt (+26/-0)
templates/lt/verify.txt (+25/-0)
templates/nl/admindbdetails.html (+63/-0)
templates/nl/admindbpreamble.html (+9/-0)
templates/nl/admindbsummary.html (+8/-0)
templates/nl/adminsubscribeack.txt (+4/-0)
templates/nl/adminunsubscribeack.txt (+3/-0)
templates/nl/admlogin.html (+36/-0)
templates/nl/approve.txt (+14/-0)
templates/nl/archidxentry.html (+4/-0)
templates/nl/archidxfoot.html (+21/-0)
templates/nl/archidxhead.html (+24/-0)
templates/nl/archlistend.html (+1/-0)
templates/nl/archliststart.html (+4/-0)
templates/nl/archtoc.html (+20/-0)
templates/nl/archtocentry.html (+12/-0)
templates/nl/archtocnombox.html (+18/-0)
templates/nl/article.html (+55/-0)
templates/nl/bounce.txt (+13/-0)
templates/nl/checkdbs.txt (+8/-0)
templates/nl/convert.txt (+33/-0)
templates/nl/cronpass.txt (+20/-0)
templates/nl/disabled.txt (+24/-0)
templates/nl/emptyarchive.html (+14/-0)
templates/nl/headfoot.html (+24/-0)
templates/nl/help.txt (+34/-0)
templates/nl/invite.txt (+21/-0)
templates/nl/listinfo.html (+141/-0)
templates/nl/masthead.txt (+14/-0)
templates/nl/newlist.txt (+38/-0)
templates/nl/nomoretoday.txt (+9/-0)
templates/nl/options.html (+298/-0)
templates/nl/postack.txt (+8/-0)
templates/nl/postauth.txt (+13/-0)
templates/nl/postheld.txt (+15/-0)
templates/nl/private.html (+54/-0)
templates/nl/probe.txt (+23/-0)
templates/nl/refuse.txt (+12/-0)
templates/nl/roster.html (+51/-0)
templates/nl/subauth.txt (+11/-0)
templates/nl/subscribe.html (+9/-0)
templates/nl/subscribeack.txt (+36/-0)
templates/nl/unsub.txt (+22/-0)
templates/nl/unsubauth.txt (+10/-0)
templates/nl/userpass.txt (+26/-0)
templates/nl/verify.txt (+22/-0)
templates/no/admindbdetails.html (+62/-0)
templates/no/admindbpreamble.html (+8/-0)
templates/no/admindbsummary.html (+10/-0)
templates/no/adminsubscribeack.txt (+2/-0)
templates/no/adminunsubscribeack.txt (+3/-0)
templates/no/admlogin.html (+39/-0)
templates/no/approve.txt (+14/-0)
templates/no/archidxfoot.html (+21/-0)
templates/no/archidxhead.html (+24/-0)
templates/no/archliststart.html (+4/-0)
templates/no/archtoc.html (+20/-0)
templates/no/archtocentry.html (+12/-0)
templates/no/archtocnombox.html (+18/-0)
templates/no/article.html (+54/-0)
templates/no/bounce.txt (+13/-0)
templates/no/checkdbs.txt (+7/-0)
templates/no/convert.txt (+33/-0)
templates/no/cronpass.txt (+19/-0)
templates/no/disabled.txt (+25/-0)
templates/no/emptyarchive.html (+14/-0)
templates/no/headfoot.html (+25/-0)
templates/no/help.txt (+33/-0)
templates/no/invite.txt (+19/-0)
templates/no/listinfo.html (+141/-0)
templates/no/masthead.txt (+15/-0)
templates/no/newlist.txt (+38/-0)
templates/no/nomoretoday.txt (+7/-0)
templates/no/options.html (+301/-0)
templates/no/postack.txt (+8/-0)
templates/no/postauth.txt (+13/-0)
templates/no/postheld.txt (+16/-0)
templates/no/private.html (+58/-0)
templates/no/refuse.txt (+13/-0)
templates/no/roster.html (+52/-0)
templates/no/subauth.txt (+10/-0)
templates/no/subscribe.html (+9/-0)
templates/no/subscribeack.txt (+37/-0)
templates/no/unsub.txt (+21/-0)
templates/no/unsubauth.txt (+10/-0)
templates/no/userpass.txt (+26/-0)
templates/no/verify.txt (+21/-0)
templates/pl/adminsubscribeack.txt (+4/-0)
templates/pl/adminunsubscribeack.txt (+3/-0)
templates/pl/admlogin.html (+39/-0)
templates/pl/approve.txt (+15/-0)
templates/pl/archidxentry.html (+4/-0)
templates/pl/archidxfoot.html (+16/-0)
templates/pl/archidxhead.html (+22/-0)
templates/pl/archlistend.html (+1/-0)
templates/pl/archliststart.html (+4/-0)
templates/pl/archtoc.html (+16/-0)
templates/pl/archtocentry.html (+9/-0)
templates/pl/archtocnombox.html (+16/-0)
templates/pl/article.html (+50/-0)
templates/pl/bounce.txt (+14/-0)
templates/pl/checkdbs.txt (+6/-0)
templates/pl/cronpass.txt (+21/-0)
templates/pl/disabled.txt (+24/-0)
templates/pl/emptyarchive.html (+16/-0)
templates/pl/help.txt (+33/-0)
templates/pl/invite.txt (+23/-0)
templates/pl/listinfo.html (+146/-0)
templates/pl/masthead.txt (+15/-0)
templates/pl/newlist.txt (+32/-0)
templates/pl/nomoretoday.txt (+8/-0)
templates/pl/options.html (+319/-0)
templates/pl/postack.txt (+8/-0)
templates/pl/postauth.txt (+12/-0)
templates/pl/postheld.txt (+16/-0)
templates/pl/private.html (+52/-0)
templates/pl/refuse.txt (+11/-0)
templates/pl/roster.html (+53/-0)
templates/pl/subauth.txt (+10/-0)
templates/pl/subscribe.html (+11/-0)
templates/pl/subscribeack.txt (+35/-0)
templates/pl/unsub.txt (+24/-0)
templates/pl/unsubauth.txt (+10/-0)
templates/pl/userpass.txt (+22/-0)
templates/pl/verify.txt (+23/-0)
templates/pt/admindbdetails.html (+72/-0)
templates/pt/admindbpreamble.html (+11/-0)
templates/pt/admindbsummary.html (+13/-0)
templates/pt/adminsubscribeack.txt (+4/-0)
templates/pt/adminunsubscribeack.txt (+3/-0)
templates/pt/admlogin.html (+39/-0)
templates/pt/approve.txt (+15/-0)
templates/pt/archidxentry.html (+4/-0)
templates/pt/archidxfoot.html (+21/-0)
templates/pt/archidxhead.html (+24/-0)
templates/pt/archlistend.html (+1/-0)
templates/pt/archliststart.html (+4/-0)
templates/pt/archtoc.html (+21/-0)
templates/pt/archtocentry.html (+12/-0)
templates/pt/article.html (+54/-0)
templates/pt/bounce.txt (+13/-0)
templates/pt/checkdbs.txt (+7/-0)
templates/pt/convert.txt (+31/-0)
templates/pt/cronpass.txt (+19/-0)
templates/pt/disabled.txt (+23/-0)
templates/pt/emptyarchive.html (+16/-0)
templates/pt/headfoot.html (+28/-0)
templates/pt/help.txt (+35/-0)
templates/pt/invite.txt (+19/-0)
templates/pt/listinfo.html (+147/-0)
templates/pt/masthead.txt (+16/-0)
templates/pt/newlist.txt (+40/-0)
templates/pt/nomoretoday.txt (+8/-0)
templates/pt/options.html (+306/-0)
templates/pt/postack.txt (+8/-0)
templates/pt/postauth.txt (+13/-0)
templates/pt/postheld.txt (+15/-0)
templates/pt/private.html (+61/-0)
templates/pt/refuse.txt (+13/-0)
templates/pt/roster.html (+53/-0)
templates/pt/subauth.txt (+11/-0)
templates/pt/subscribe.html (+9/-0)
templates/pt/subscribeack.txt (+34/-0)
templates/pt/unsub.txt (+23/-0)
templates/pt/unsubauth.txt (+11/-0)
templates/pt/userpass.txt (+26/-0)
templates/pt/verify.txt (+23/-0)
templates/pt_BR/admindbdetails.html (+72/-0)
templates/pt_BR/admindbpreamble.html (+11/-0)
templates/pt_BR/admindbsummary.html (+14/-0)
templates/pt_BR/adminsubscribeack.txt (+4/-0)
templates/pt_BR/adminunsubscribeack.txt (+3/-0)
templates/pt_BR/admlogin.html (+39/-0)
templates/pt_BR/approve.txt (+16/-0)
templates/pt_BR/archidxentry.html (+4/-0)
templates/pt_BR/archidxfoot.html (+21/-0)
templates/pt_BR/archidxhead.html (+24/-0)
templates/pt_BR/archlistend.html (+1/-0)
templates/pt_BR/archliststart.html (+4/-0)
templates/pt_BR/archtoc.html (+20/-0)
templates/pt_BR/archtocentry.html (+12/-0)
templates/pt_BR/article.html (+55/-0)
templates/pt_BR/bounce.txt (+13/-0)
templates/pt_BR/checkdbs.txt (+7/-0)
templates/pt_BR/convert.txt (+37/-0)
templates/pt_BR/cronpass.txt (+21/-0)
templates/pt_BR/disabled.txt (+27/-0)
templates/pt_BR/emptyarchive.html (+15/-0)
templates/pt_BR/headfoot.html (+30/-0)
templates/pt_BR/help.txt (+34/-0)
templates/pt_BR/invite.txt (+20/-0)
templates/pt_BR/listinfo.html (+148/-0)
templates/pt_BR/masthead.txt (+16/-0)
templates/pt_BR/newlist.txt (+41/-0)
templates/pt_BR/nomoretoday.txt (+8/-0)
templates/pt_BR/options.html (+325/-0)
templates/pt_BR/postack.txt (+8/-0)
templates/pt_BR/postauth.txt (+13/-0)
templates/pt_BR/postheld.txt (+15/-0)
templates/pt_BR/private.html (+59/-0)
templates/pt_BR/refuse.txt (+13/-0)
templates/pt_BR/roster.html (+53/-0)
templates/pt_BR/subauth.txt (+11/-0)
templates/pt_BR/subscribe.html (+9/-0)
templates/pt_BR/subscribeack.txt (+32/-0)
templates/pt_BR/unsub.txt (+23/-0)
templates/pt_BR/unsubauth.txt (+12/-0)
templates/pt_BR/userpass.txt (+25/-0)
templates/pt_BR/verify.txt (+23/-0)
templates/ro/admindbdetails.html (+65/-0)
templates/ro/admindbpreamble.html (+11/-0)
templates/ro/admindbsummary.html (+12/-0)
templates/ro/adminsubscribeack.txt (+2/-0)
templates/ro/adminunsubscribeack.txt (+2/-0)
templates/ro/admlogin.html (+38/-0)
templates/ro/approve.txt (+15/-0)
templates/ro/archidxentry.html (+4/-0)
templates/ro/archidxfoot.html (+20/-0)
templates/ro/archidxhead.html (+24/-0)
templates/ro/archlistend.html (+1/-0)
templates/ro/archliststart.html (+4/-0)
templates/ro/archtoc.html (+21/-0)
templates/ro/archtocentry.html (+12/-0)
templates/ro/article.html (+54/-0)
templates/ro/bounce.txt (+13/-0)
templates/ro/checkdbs.txt (+7/-0)
templates/ro/convert.txt (+34/-0)
templates/ro/cronpass.txt (+19/-0)
templates/ro/disabled.txt (+25/-0)
templates/ro/emptyarchive.html (+16/-0)
templates/ro/headfoot.html (+25/-0)
templates/ro/help.txt (+36/-0)
templates/ro/invite.txt (+21/-0)
templates/ro/listinfo.html (+142/-0)
templates/ro/masthead.txt (+15/-0)
templates/ro/newlist.txt (+38/-0)
templates/ro/nomoretoday.txt (+9/-0)
templates/ro/options.html (+293/-0)
templates/ro/postack.txt (+8/-0)
templates/ro/postauth.txt (+13/-0)
templates/ro/postheld.txt (+15/-0)
templates/ro/private.html (+59/-0)
templates/ro/refuse.txt (+13/-0)
templates/ro/roster.html (+53/-0)
templates/ro/subauth.txt (+11/-0)
templates/ro/subscribe.html (+10/-0)
templates/ro/subscribeack.txt (+35/-0)
templates/ro/unsub.txt (+24/-0)
templates/ro/unsubauth.txt (+11/-0)
templates/ro/userpass.txt (+26/-0)
templates/ro/verify.txt (+24/-0)
templates/ru/adminaddrchgack.txt (+5/-0)
templates/ru/admindbdetails.html (+61/-0)
templates/ru/admindbpreamble.html (+10/-0)
templates/ru/admindbsummary.html (+11/-0)
templates/ru/adminsubscribeack.txt (+3/-0)
templates/ru/adminunsubscribeack.txt (+3/-0)
templates/ru/admlogin.html (+34/-0)
templates/ru/approve.txt (+15/-0)
templates/ru/archidxentry.html (+1/-0)
templates/ru/archidxfoot.html (+19/-0)
templates/ru/archidxhead.html (+23/-0)
templates/ru/archlistend.html (+1/-0)
templates/ru/archliststart.html (+4/-0)
templates/ru/archtoc.html (+19/-0)
templates/ru/archtocentry.html (+12/-0)
templates/ru/archtocnombox.html (+18/-0)
templates/ru/article.html (+53/-0)
templates/ru/bounce.txt (+13/-0)
templates/ru/checkdbs.txt (+8/-0)
templates/ru/convert.txt (+29/-0)
templates/ru/cronpass.txt (+20/-0)
templates/ru/disabled.txt (+22/-0)
templates/ru/emptyarchive.html (+14/-0)
templates/ru/headfoot.html (+24/-0)
templates/ru/help.txt (+34/-0)
templates/ru/invite.txt (+20/-0)
templates/ru/listinfo.html (+133/-0)
templates/ru/masthead.txt (+14/-0)
templates/ru/newlist.txt (+38/-0)
templates/ru/nomoretoday.txt (+9/-0)
templates/ru/options.html (+289/-0)
templates/ru/postack.txt (+8/-0)
templates/ru/postauth.txt (+12/-0)
templates/ru/postheld.txt (+15/-0)
templates/ru/private.html (+56/-0)
templates/ru/probe.txt (+23/-0)
templates/ru/refuse.txt (+13/-0)
templates/ru/roster.html (+47/-0)
templates/ru/subauth.txt (+8/-0)
templates/ru/subscribe.html (+8/-0)
templates/ru/subscribeack.txt (+36/-0)
templates/ru/unsub.txt (+22/-0)
templates/ru/unsubauth.txt (+8/-0)
templates/ru/userpass.txt (+22/-0)
templates/ru/verify.txt (+22/-0)
templates/sk/admindbdetails.html (+62/-0)
templates/sk/admindbpreamble.html (+14/-0)
templates/sk/admindbsummary.html (+18/-0)
templates/sk/adminsubscribeack.txt (+3/-0)
templates/sk/adminunsubscribeack.txt (+2/-0)
templates/sk/admlogin.html (+37/-0)
templates/sk/approve.txt (+15/-0)
templates/sk/archidxentry.html (+4/-0)
templates/sk/archidxfoot.html (+21/-0)
templates/sk/archidxhead.html (+24/-0)
templates/sk/archlistend.html (+1/-0)
templates/sk/archliststart.html (+4/-0)
templates/sk/archtoc.html (+20/-0)
templates/sk/archtocentry.html (+12/-0)
templates/sk/archtocnombox.html (+18/-0)
templates/sk/article.html (+53/-0)
templates/sk/bounce.txt (+15/-0)
templates/sk/checkdbs.txt (+7/-0)
templates/sk/convert.txt (+32/-0)
templates/sk/cronpass.txt (+19/-0)
templates/sk/disabled.txt (+23/-0)
templates/sk/emptyarchive.html (+15/-0)
templates/sk/headfoot.html (+30/-0)
templates/sk/help.txt (+36/-0)
templates/sk/invite.txt (+18/-0)
templates/sk/listinfo.html (+151/-0)
templates/sk/masthead.txt (+15/-0)
templates/sk/newlist.txt (+37/-0)
templates/sk/nomoretoday.txt (+11/-0)
templates/sk/options.html (+317/-0)
templates/sk/postack.txt (+8/-0)
templates/sk/postauth.txt (+10/-0)
templates/sk/postheld.txt (+16/-0)
templates/sk/private.html (+57/-0)
templates/sk/probe.txt (+27/-0)
templates/sk/refuse.txt (+12/-0)
templates/sk/roster.html (+53/-0)
templates/sk/subauth.txt (+8/-0)
templates/sk/subscribe.html (+9/-0)
templates/sk/subscribeack.txt (+34/-0)
templates/sk/unsub.txt (+20/-0)
templates/sk/unsubauth.txt (+8/-0)
templates/sk/userpass.txt (+23/-0)
templates/sk/verify.txt (+22/-0)
templates/sl/admindbdetails.html (+62/-0)
templates/sl/admindbpreamble.html (+9/-0)
templates/sl/admindbsummary.html (+10/-0)
templates/sl/adminsubscribeack.txt (+4/-0)
templates/sl/adminunsubscribeack.txt (+2/-0)
templates/sl/admlogin.html (+39/-0)
templates/sl/approve.txt (+14/-0)
templates/sl/archidxentry.html (+4/-0)
templates/sl/archidxfoot.html (+21/-0)
templates/sl/archidxhead.html (+24/-0)
templates/sl/archlistend.html (+1/-0)
templates/sl/archliststart.html (+4/-0)
templates/sl/archtoc.html (+20/-0)
templates/sl/archtocentry.html (+12/-0)
templates/sl/article.html (+55/-0)
templates/sl/bounce.txt (+13/-0)
templates/sl/checkdbs.txt (+7/-0)
templates/sl/convert.txt (+33/-0)
templates/sl/cronpass.txt (+18/-0)
templates/sl/disabled.txt (+23/-0)
templates/sl/emptyarchive.html (+15/-0)
templates/sl/headfoot.html (+27/-0)
templates/sl/help.txt (+32/-0)
templates/sl/invite.txt (+18/-0)
templates/sl/listinfo.html (+145/-0)
templates/sl/masthead.txt (+13/-0)
templates/sl/newlist.txt (+36/-0)
templates/sl/nomoretoday.txt (+9/-0)
templates/sl/options.html (+309/-0)
templates/sl/postack.txt (+8/-0)
templates/sl/postauth.txt (+11/-0)
templates/sl/postheld.txt (+16/-0)
templates/sl/private.html (+60/-0)
templates/sl/refuse.txt (+11/-0)
templates/sl/roster.html (+53/-0)
templates/sl/subauth.txt (+8/-0)
templates/sl/subscribe.html (+9/-0)
templates/sl/subscribeack.txt (+34/-0)
templates/sl/unsub.txt (+22/-0)
templates/sl/unsubauth.txt (+9/-0)
templates/sl/userpass.txt (+23/-0)
templates/sl/verify.txt (+23/-0)
templates/sr/admindbdetails.html (+57/-0)
templates/sr/admindbpreamble.html (+6/-0)
templates/sr/admindbsummary.html (+9/-0)
templates/sr/adminsubscribeack.txt (+4/-0)
templates/sr/adminunsubscribeack.txt (+3/-0)
templates/sr/admlogin.html (+35/-0)
templates/sr/approve.txt (+11/-0)
templates/sr/archidxentry.html (+4/-0)
templates/sr/archidxfoot.html (+21/-0)
templates/sr/archidxhead.html (+21/-0)
templates/sr/archlistend.html (+1/-0)
templates/sr/archliststart.html (+7/-0)
templates/sr/archtoc.html (+20/-0)
templates/sr/archtocentry.html (+10/-0)
templates/sr/article.html (+50/-0)
templates/sr/bounce.txt (+13/-0)
templates/sr/checkdbs.txt (+8/-0)
templates/sr/convert.txt (+31/-0)
templates/sr/cronpass.txt (+19/-0)
templates/sr/disabled.txt (+25/-0)
templates/sr/emptyarchive.html (+14/-0)
templates/sr/handle_opts.html (+9/-0)
templates/sr/headfoot.html (+15/-0)
templates/sr/help.txt (+32/-0)
templates/sr/invite.txt (+22/-0)
templates/sr/listinfo.html (+128/-0)
templates/sr/masthead.txt (+19/-0)
templates/sr/newlist.txt (+35/-0)
templates/sr/nomoretoday.txt (+8/-0)
templates/sr/options.html (+269/-0)
templates/sr/postack.txt (+8/-0)
templates/sr/postauth.txt (+11/-0)
templates/sr/postheld.txt (+14/-0)
templates/sr/private.html (+55/-0)
templates/sr/refuse.txt (+13/-0)
templates/sr/roster.html (+51/-0)
templates/sr/subauth.txt (+8/-0)
templates/sr/subscribe.html (+9/-0)
templates/sr/subscribeack.txt (+35/-0)
templates/sr/unsub.txt (+22/-0)
templates/sr/unsubauth.txt (+8/-0)
templates/sr/userpass.txt (+25/-0)
templates/sr/verify.txt (+21/-0)
templates/sv/admindbdetails.html (+20/-0)
templates/sv/admindbpreamble.html (+4/-0)
templates/sv/admindbsummary.html (+3/-0)
templates/sv/adminsubscribeack.txt (+2/-0)
templates/sv/adminunsubscribeack.txt (+3/-0)
templates/sv/admlogin.html (+29/-0)
templates/sv/approve.txt (+14/-0)
templates/sv/archtoc.html (+20/-0)
templates/sv/archtocentry.html (+12/-0)
templates/sv/article.html (+56/-0)
templates/sv/bounce.txt (+13/-0)
templates/sv/checkdbs.txt (+7/-0)
templates/sv/convert.txt (+33/-0)
templates/sv/cronpass.txt (+19/-0)
templates/sv/disabled.txt (+25/-0)
templates/sv/emptyarchive.html (+11/-0)
templates/sv/headfoot.html (+9/-0)
templates/sv/help.txt (+33/-0)
templates/sv/invite.txt (+19/-0)
templates/sv/listinfo.html (+127/-0)
templates/sv/masthead.txt (+15/-0)
templates/sv/newlist.txt (+39/-0)
templates/sv/nomoretoday.txt (+7/-0)
templates/sv/options.html (+253/-0)
templates/sv/postack.txt (+7/-0)
templates/sv/postauth.txt (+13/-0)
templates/sv/postheld.txt (+16/-0)
templates/sv/private.html (+49/-0)
templates/sv/refuse.txt (+13/-0)
templates/sv/roster.html (+48/-0)
templates/sv/subauth.txt (+10/-0)
templates/sv/subscribe.html (+9/-0)
templates/sv/subscribeack.txt (+35/-0)
templates/sv/unsub.txt (+19/-0)
templates/sv/unsubauth.txt (+10/-0)
templates/sv/userpass.txt (+26/-0)
templates/sv/verify.txt (+20/-0)
templates/tr/admindbdetails.html (+66/-0)
templates/tr/admindbpreamble.html (+12/-0)
templates/tr/admindbsummary.html (+14/-0)
templates/tr/adminsubscribeack.txt (+5/-0)
templates/tr/adminunsubscribeack.txt (+4/-0)
templates/tr/admlogin.html (+41/-0)
templates/tr/approve.txt (+16/-0)
templates/tr/archidxentry.html (+4/-0)
templates/tr/archidxfoot.html (+22/-0)
templates/tr/archidxhead.html (+25/-0)
templates/tr/archlistend.html (+1/-0)
templates/tr/archliststart.html (+5/-0)
templates/tr/archtoc.html (+21/-0)
templates/tr/archtocentry.html (+13/-0)
templates/tr/archtocnombox.html (+19/-0)
templates/tr/article.html (+56/-0)
templates/tr/bounce.txt (+15/-0)
templates/tr/checkdbs.txt (+8/-0)
templates/tr/convert.txt (+34/-0)
templates/tr/cronpass.txt (+20/-0)
templates/tr/disabled.txt (+26/-0)
templates/tr/emptyarchive.html (+16/-0)
templates/tr/headfoot.html (+28/-0)
templates/tr/help.txt (+35/-0)
templates/tr/invite.txt (+22/-0)
templates/tr/listinfo.html (+149/-0)
templates/tr/masthead.txt (+17/-0)
templates/tr/newlist.txt (+41/-0)
templates/tr/nomoretoday.txt (+10/-0)
templates/tr/options.html (+311/-0)
templates/tr/postack.txt (+10/-0)
templates/tr/postauth.txt (+14/-0)
templates/tr/postheld.txt (+17/-0)
templates/tr/private.html (+61/-0)
templates/tr/refuse.txt (+14/-0)
templates/tr/roster.html (+54/-0)
templates/tr/subauth.txt (+11/-0)
templates/tr/subscribe.html (+9/-0)
templates/tr/subscribeack.txt (+35/-0)
templates/tr/unsub.txt (+23/-0)
templates/tr/unsubauth.txt (+12/-0)
templates/tr/userpass.txt (+25/-0)
templates/tr/verify.txt (+22/-0)
templates/uk/admindbdetails.html (+67/-0)
templates/uk/admindbpreamble.html (+10/-0)
templates/uk/admindbsummary.html (+13/-0)
templates/uk/adminsubscribeack.txt (+4/-0)
templates/uk/adminunsubscribeack.txt (+3/-0)
templates/uk/admlogin.html (+40/-0)
templates/uk/approve.txt (+15/-0)
templates/uk/archidxentry.html (+4/-0)
templates/uk/archidxfoot.html (+20/-0)
templates/uk/archidxhead.html (+24/-0)
templates/uk/archlistend.html (+1/-0)
templates/uk/archliststart.html (+4/-0)
templates/uk/archtoc.html (+20/-0)
templates/uk/archtocentry.html (+12/-0)
templates/uk/archtocnombox.html (+18/-0)
templates/uk/article.html (+54/-0)
templates/uk/bounce.txt (+13/-0)
templates/uk/checkdbs.txt (+7/-0)
templates/uk/convert.txt (+30/-0)
templates/uk/cronpass.txt (+19/-0)
templates/uk/disabled.txt (+24/-0)
templates/uk/emptyarchive.html (+16/-0)
templates/uk/headfoot.html (+24/-0)
templates/uk/help.txt (+33/-0)
templates/uk/invite.txt (+23/-0)
templates/uk/listinfo.html (+144/-0)
templates/uk/masthead.txt (+13/-0)
templates/uk/newlist.txt (+38/-0)
templates/uk/nomoretoday.txt (+9/-0)
templates/uk/options.html (+306/-0)
templates/uk/postack.txt (+8/-0)
templates/uk/postauth.txt (+11/-0)
templates/uk/postheld.txt (+16/-0)
templates/uk/private.html (+59/-0)
templates/uk/probe.txt (+25/-0)
templates/uk/refuse.txt (+13/-0)
templates/uk/roster.html (+53/-0)
templates/uk/subauth.txt (+9/-0)
templates/uk/subscribe.html (+9/-0)
templates/uk/subscribeack.txt (+35/-0)
templates/uk/unsub.txt (+24/-0)
templates/uk/unsubauth.txt (+9/-0)
templates/uk/userpass.txt (+25/-0)
templates/uk/verify.txt (+24/-0)
templates/vi/admindbdetails.html (+27/-0)
templates/vi/admindbpreamble.html (+6/-0)
templates/vi/admindbsummary.html (+7/-0)
templates/vi/adminsubscribeack.txt (+4/-0)
templates/vi/adminunsubscribeack.txt (+3/-0)
templates/vi/admlogin.html (+32/-0)
templates/vi/approve.txt (+13/-0)
templates/vi/archidxentry.html (+4/-0)
templates/vi/archidxfoot.html (+20/-0)
templates/vi/archidxhead.html (+24/-0)
templates/vi/archlistend.html (+1/-0)
templates/vi/archliststart.html (+4/-0)
templates/vi/archtoc.html (+20/-0)
templates/vi/archtocentry.html (+12/-0)
templates/vi/archtocnombox.html (+18/-0)
templates/vi/article.html (+54/-0)
templates/vi/bounce.txt (+13/-0)
templates/vi/checkdbs.txt (+5/-0)
templates/vi/convert.txt (+18/-0)
templates/vi/cronpass.txt (+10/-0)
templates/vi/disabled.txt (+18/-0)
templates/vi/emptyarchive.html (+13/-0)
templates/vi/headfoot.html (+18/-0)
templates/vi/help.txt (+23/-0)
templates/vi/invite.txt (+15/-0)
templates/vi/listinfo.html (+135/-0)
templates/vi/masthead.txt (+12/-0)
templates/vi/newlist.txt (+25/-0)
templates/vi/nomoretoday.txt (+3/-0)
templates/vi/options.html (+254/-0)
templates/vi/postack.txt (+8/-0)
templates/vi/postauth.txt (+12/-0)
templates/vi/postheld.txt (+13/-0)
templates/vi/private.html (+51/-0)
templates/vi/probe.txt (+15/-0)
templates/vi/refuse.txt (+11/-0)
templates/vi/roster.html (+50/-0)
templates/vi/subauth.txt (+10/-0)
templates/vi/subscribe.html (+9/-0)
templates/vi/subscribeack.txt (+25/-0)
templates/vi/unsub.txt (+13/-0)
templates/vi/unsubauth.txt (+10/-0)
templates/vi/userpass.txt (+17/-0)
templates/vi/verify.txt (+13/-0)
templates/zh_CN/admindbdetails.html (+46/-0)
templates/zh_CN/admindbpreamble.html (+7/-0)
templates/zh_CN/admindbsummary.html (+8/-0)
templates/zh_CN/adminsubscribeack.txt (+4/-0)
templates/zh_CN/adminunsubscribeack.txt (+3/-0)
templates/zh_CN/admlogin.html (+36/-0)
templates/zh_CN/approve.txt (+11/-0)
templates/zh_CN/archidxentry.html (+4/-0)
templates/zh_CN/archidxfoot.html (+20/-0)
templates/zh_CN/archidxhead.html (+24/-0)
templates/zh_CN/archlistend.html (+1/-0)
templates/zh_CN/archliststart.html (+4/-0)
templates/zh_CN/archtoc.html (+20/-0)
templates/zh_CN/archtocentry.html (+12/-0)
templates/zh_CN/archtocnombox.html (+18/-0)
templates/zh_CN/article.html (+54/-0)
templates/zh_CN/bounce.txt (+12/-0)
templates/zh_CN/checkdbs.txt (+5/-0)
templates/zh_CN/convert.txt (+31/-0)
templates/zh_CN/cronpass.txt (+14/-0)
templates/zh_CN/disabled.txt (+23/-0)
templates/zh_CN/emptyarchive.html (+14/-0)
templates/zh_CN/headfoot.html (+16/-0)
templates/zh_CN/help.txt (+27/-0)
templates/zh_CN/invite.txt (+17/-0)
templates/zh_CN/listinfo.html (+140/-0)
templates/zh_CN/masthead.txt (+15/-0)
templates/zh_CN/newlist.txt (+30/-0)
templates/zh_CN/nomoretoday.txt (+8/-0)
templates/zh_CN/options.html (+292/-0)
templates/zh_CN/postack.txt (+8/-0)
templates/zh_CN/postauth.txt (+12/-0)
templates/zh_CN/postheld.txt (+14/-0)
templates/zh_CN/private.html (+56/-0)
templates/zh_CN/probe.txt (+17/-0)
templates/zh_CN/refuse.txt (+11/-0)
templates/zh_CN/roster.html (+50/-0)
templates/zh_CN/subauth.txt (+10/-0)
templates/zh_CN/subscribe.html (+8/-0)
templates/zh_CN/subscribeack.txt (+31/-0)
templates/zh_CN/unsub.txt (+18/-0)
templates/zh_CN/unsubauth.txt (+10/-0)
templates/zh_CN/userpass.txt (+21/-0)
templates/zh_CN/verify.txt (+18/-0)
templates/zh_TW/admindbpreamble.html (+22/-0)
templates/zh_TW/adminsubscribeack.txt (+3/-0)
templates/zh_TW/adminunsubscribeack.txt (+4/-0)
templates/zh_TW/admlogin.html (+32/-0)
templates/zh_TW/approve.txt (+12/-0)
templates/zh_TW/bounce.txt (+12/-0)
templates/zh_TW/checkdbs.txt (+8/-0)
templates/zh_TW/convert.txt (+40/-0)
templates/zh_TW/cronpass.txt (+16/-0)
templates/zh_TW/handle_opts.html (+9/-0)
templates/zh_TW/headfoot.html (+23/-0)
templates/zh_TW/help.txt (+33/-0)
templates/zh_TW/listinfo.html (+133/-0)
templates/zh_TW/masthead.txt (+18/-0)
templates/zh_TW/newlist.txt (+32/-0)
templates/zh_TW/options.html (+152/-0)
templates/zh_TW/postack.txt (+7/-0)
templates/zh_TW/postauth.txt (+12/-0)
templates/zh_TW/postheld.txt (+11/-0)
templates/zh_TW/refuse.txt (+11/-0)
templates/zh_TW/roster.html (+50/-0)
templates/zh_TW/subauth.txt (+10/-0)
templates/zh_TW/subscribe.html (+9/-0)
templates/zh_TW/subscribeack.txt (+34/-0)
templates/zh_TW/userpass.txt (+17/-0)
templates/zh_TW/verify.txt (+17/-0)
tests/EmailBase.py (+77/-0)
tests/Makefile.in (+84/-0)
tests/TestBase.py (+72/-0)
tests/bounces/Makefile.in (+69/-0)
tests/bounces/aol_01.txt (+19/-0)
tests/bounces/bounce_01.txt (+95/-0)
tests/bounces/bounce_02.txt (+36/-0)
tests/bounces/bounce_03.txt (+95/-0)
tests/bounces/dsn_01.txt (+141/-0)
tests/bounces/dsn_02.txt (+187/-0)
tests/bounces/dsn_03.txt (+143/-0)
tests/bounces/dsn_04.txt (+148/-0)
tests/bounces/dsn_05.txt (+125/-0)
tests/bounces/dsn_06.txt (+122/-0)
tests/bounces/dsn_07.txt (+121/-0)
tests/bounces/dsn_08.txt (+131/-0)
tests/bounces/dsn_09.txt (+85/-0)
tests/bounces/dsn_10.txt (+66/-0)
tests/bounces/dsn_11.txt (+176/-0)
tests/bounces/dsn_12.txt (+40/-0)
tests/bounces/dsn_13.txt (+128/-0)
tests/bounces/dsn_14.txt (+110/-0)
tests/bounces/dsn_15.txt (+166/-0)
tests/bounces/dsn_16.txt (+65/-0)
tests/bounces/dsn_17.txt (+101/-0)
tests/bounces/dsn_18.txt (+52/-0)
tests/bounces/dumbass_01.txt (+109/-0)
tests/bounces/exim_01.txt (+58/-0)
tests/bounces/groupwise_01.txt (+107/-0)
tests/bounces/groupwise_02.txt (+68/-0)
tests/bounces/groupwise_03.txt (+91/-0)
tests/bounces/hotpop_01.txt (+78/-0)
tests/bounces/llnl_01.txt (+66/-0)
tests/bounces/microsoft_01.txt (+48/-0)
tests/bounces/microsoft_02.txt (+55/-0)
tests/bounces/microsoft_03.txt (+65/-0)
tests/bounces/netscape_01.txt (+76/-0)
tests/bounces/newmailru_01.txt (+52/-0)
tests/bounces/postfix_01.txt (+67/-0)
tests/bounces/postfix_02.txt (+60/-0)
tests/bounces/postfix_03.txt (+80/-0)
tests/bounces/postfix_04.txt (+85/-0)
tests/bounces/postfix_05.txt (+82/-0)
tests/bounces/qmail_01.txt (+23/-0)
tests/bounces/qmail_02.txt (+26/-0)
tests/bounces/qmail_03.txt (+27/-0)
tests/bounces/qmail_04.txt (+29/-0)
tests/bounces/qmail_05.txt (+27/-0)
tests/bounces/qmail_06.txt (+32/-0)
tests/bounces/qmail_07.txt (+25/-0)
tests/bounces/qmail_08.txt (+24/-0)
tests/bounces/sendmail_01.txt (+60/-0)
tests/bounces/simple_01.txt (+109/-0)
tests/bounces/simple_02.txt (+59/-0)
tests/bounces/simple_03.txt (+68/-0)
tests/bounces/simple_04.txt (+60/-0)
tests/bounces/simple_05.txt (+25/-0)
tests/bounces/simple_06.txt (+24/-0)
tests/bounces/simple_07.txt (+21/-0)
tests/bounces/simple_08.txt (+52/-0)
tests/bounces/simple_09.txt (+27/-0)
tests/bounces/simple_10.txt (+45/-0)
tests/bounces/simple_11.txt (+68/-0)
tests/bounces/simple_12.txt (+24/-0)
tests/bounces/simple_13.txt (+35/-0)
tests/bounces/simple_14.txt (+29/-0)
tests/bounces/simple_15.txt (+40/-0)
tests/bounces/simple_16.txt (+32/-0)
tests/bounces/simple_17.txt (+33/-0)
tests/bounces/simple_18.txt (+33/-0)
tests/bounces/simple_19.txt (+35/-0)
tests/bounces/simple_20.txt (+27/-0)
tests/bounces/simple_21.txt (+46/-0)
tests/bounces/simple_22.txt (+25/-0)
tests/bounces/simple_23.txt (+24/-0)
tests/bounces/simple_24.txt (+33/-0)
tests/bounces/simple_25.txt (+54/-0)
tests/bounces/simple_26.txt (+39/-0)
tests/bounces/simple_27.txt (+49/-0)
tests/bounces/simple_28.txt (+46/-0)
tests/bounces/simple_29.txt (+29/-0)
tests/bounces/simple_30.txt (+53/-0)
tests/bounces/simple_31.txt (+49/-0)
tests/bounces/simple_32.txt (+53/-0)
tests/bounces/simple_33.txt (+54/-0)
tests/bounces/simple_34.txt (+31/-0)
tests/bounces/simple_35.txt (+27/-0)
tests/bounces/simple_36.txt (+24/-0)
tests/bounces/simple_37.txt (+33/-0)
tests/bounces/simple_38.txt (+46/-0)
tests/bounces/simple_39.txt (+31/-0)
tests/bounces/simple_40.txt (+29/-0)
tests/bounces/simple_41.txt (+27/-0)
tests/bounces/simple_44.txt (+28/-0)
tests/bounces/sina_01.txt (+67/-0)
tests/bounces/smtp32_01.txt (+55/-0)
tests/bounces/smtp32_02.txt (+55/-0)
tests/bounces/smtp32_03.txt (+56/-0)
tests/bounces/smtp32_04.txt (+29/-0)
tests/bounces/smtp32_05.txt (+24/-0)
tests/bounces/smtp32_06.txt (+24/-0)
tests/bounces/smtp32_07.txt (+29/-0)
tests/bounces/yahoo_01.txt (+29/-0)
tests/bounces/yahoo_02.txt (+15/-0)
tests/bounces/yahoo_03.txt (+45/-0)
tests/bounces/yahoo_04.txt (+48/-0)
tests/bounces/yahoo_05.txt (+48/-0)
tests/bounces/yahoo_06.txt (+54/-0)
tests/bounces/yahoo_07.txt (+54/-0)
tests/bounces/yahoo_08.txt (+60/-0)
tests/bounces/yahoo_09.txt (+48/-0)
tests/bounces/yahoo_10.txt (+30/-0)
tests/bounces/yahoo_11.txt (+16/-0)
tests/bounces/yahoo_12.txt (+18/-0)
tests/bounces/yale_01.txt (+65/-0)
tests/fblast.py (+61/-0)
tests/msgs/Makefile.in (+69/-0)
tests/msgs/bad_01.txt (+62/-0)
tests/onebounce.py (+96/-0)
tests/test_bounces.py (+259/-0)
tests/test_handlers.py (+2172/-0)
tests/test_lockfile.py (+49/-0)
tests/test_membership.py (+386/-0)
tests/test_message.py (+120/-0)
tests/test_runners.py (+124/-0)
tests/test_safedict.py (+107/-0)
tests/test_security_mgr.py (+279/-0)
tests/test_smtp.py (+74/-0)
tests/testall.py (+40/-0)
Conflict adding file .bzrignore.  Moved existing file to .bzrignore.moved.
Conflict adding file cron.  Moved existing file to cron.moved.
Conflict adding file src.  Moved existing file to src.moved.
To merge this branch: bzr merge lp:mailman/2.1
Reviewer Review Type Date Requested Status
Mark Sapiro Disapprove
Review via email: mp+480481@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Mark Sapiro (msapiro) wrote :

lp:mailman is a very early version of Mailman 3 and is no longer relevant. Mailman 3 is currently on GitLab and while it currently doesn't support all features of Mailman 2.1 it has diverged so far from Mailman 2.1 that merging all or even selected parts of Mailman 2.1 would be a disaster.

review: Disapprove

Unmerged revisions

1894. By Mark Sapiro

Update reCAPTCHA urls for global use.

1893. By Mark Sapiro

Improved fix for LP: #2017813.

1892. By Mark Sapiro

Fixed yet another possible list membership leak via the user options CGI.

1891. By Mark Sapiro

Fixed another possible list membership leak via the user options CGI.

1890. By Mark Sapiro

Fixed a possible list membership leak via the user options CGI.

1889. By Mark Sapiro

Fix German translation of Esperanto.

1888. By Mark Sapiro

Improve fix for lp:1961762 in prior commit.

1887. By Mark Sapiro

Avoid 500 Internal Server Error for non-member with private roster.

1886. By Mark Sapiro

Fix test for valid header following From_ line.

1885. By Mark Sapiro

Bumped branch version to: 2.1.39

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2025-02-01 12:21:11 +0000
4@@ -0,0 +1,26 @@
5+Mailman/Defaults.py
6+Mailman/mm_cfg.py.dist
7+build
8+config.log
9+config.status
10+cron/crontab.in
11+misc/JapaneseCodecs-1.4.11
12+misc/KoreanCodecs-2.0.5
13+misc/email-2.5.8
14+misc/mailman
15+misc/paths.py
16+src/admin
17+src/admindb
18+src/confirm
19+src/create
20+src/edithtml
21+src/listinfo
22+src/mailman
23+src/options
24+src/private
25+src/rmlist
26+src/roster
27+src/subscribe
28+Makefile
29+*.mo
30+autom4te.cache
31
32=== renamed file '.bzrignore' => '.bzrignore.moved'
33=== added file 'ACKNOWLEDGMENTS'
34--- ACKNOWLEDGMENTS 1970-01-01 00:00:00 +0000
35+++ ACKNOWLEDGMENTS 2025-02-01 12:21:11 +0000
36@@ -0,0 +1,251 @@
37+Mailman - The GNU Mailing List Management System
38+Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
39+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
40+
41+The following folks are or have been core developers of Mailman (in reverse
42+alphabetical order):
43+
44+ Barry Warsaw, Mailman's yappy guard dog
45+ Thomas Wouters, Mailman's Dutch treat
46+ John Viega, Mailman's inventor
47+ Mark Sapiro, Mailman's compulsive responder
48+ Harald Meland, Norse Mailman
49+ Ken Manheimer, Mailman's savior
50+ Tokio Kikuchi, Mailman's weatherman
51+ Scott Cotton, Cookie-Monster
52+
53+They can be contacted directly via mailman-cabal@python.org.
54+
55+Here is the list of other contributors who have donated large bits of
56+code, and have assigned copyright for contributions to the FSF:
57+
58+ Juan Carlos Rey Anaya
59+ Richard Barrett
60+ Stephan Berndts
61+ Norbert Bollow
62+ Ben Gertzfield
63+ Victoriano Giralt
64+ Mads Kiilerich
65+ The Dragon De Monsyne
66+ Les Niles
67+ Terri Oda
68+ Simone Piunno
69+
70+Thanks also go to the following people for their important contributions in
71+other aspects of the Mailman project:
72+
73+ Brad Knowles
74+ JC Dill
75+
76+Thanks also to Dragon for his winning Mailman logo contribution, and
77+to Terri Oda for the neat shortcut icon and the member documentation.
78+
79+Control.com sponsored development of several Mailman 2.1 features,
80+including topics filters, external membership sources, and initial
81+virtual mailing list support. My thanks especially to Dan Pierson and
82+Ken Crater from Control.com.
83+
84+Here is the list of other people who have contributed useful ideas,
85+suggestions, bug fixes, testing, etc., or who have been very helpful
86+in answering questions on mailman-users.
87+
88+ David Abrahams
89+ William Ahern
90+ Terry Allen
91+ Jose Paulo Moitinho de Almeida
92+ Sven Anderson
93+ Matthias Andree
94+ Anton Antonov
95+ Mike Avery
96+ Stonewall Ballard
97+ Moreno Baricevic
98+ Jeff Berliner
99+ Stuart Bishop
100+ David Blomquist
101+ Bojan
102+ Søren Bondrup
103+ Grant Bowman
104+ Alessio Bragadini
105+ J. D. Bronson
106+ Stan Bubrouski
107+ Daniel Buchmann
108+ Ben Burnett
109+ Ted Cabeen
110+ Mentor Cana
111+ John Carnes
112+ Julio A. Cartaya
113+ Claudio Cattazzo
114+ Donn Cave
115+ David Champion
116+ Hye-Shik Chang
117+ Eric D. Christensen
118+ Tom G. Christensen
119+ Paul Cox
120+ Stefaniu Criste
121+ Robert Daeley
122+ Ned Dawes
123+ Emilio Delgado
124+ John Dennis
125+ Stefan Divjak
126+ Maximillian Dornseif
127+ Fred Drake
128+ Maxim Dzumanenko
129+ Piarres Beobide Egaña
130+ Rob Ellis
131+ Kerem Erkan
132+ Fil
133+ Patrick Finnerty
134+ Bob Fleck
135+ Erik Forsberg
136+ Darrell Fuhriman
137+ Robert GarrigĂ³s
138+ Carson Gaspar
139+ Pascal GEORGE
140+ Vadim Getmanshchuk
141+ David Gibbs
142+ Dmitri I GOULIAEV
143+ Terry Grace
144+ Federico Grau
145+ Pekka Haavisto
146+ David Habben
147+ Stig Hackvan
148+ Jeff Hahn
149+ Terry Hardie
150+ Paul Hebble
151+ Tollef Fog Heen
152+ Peer Heinlein
153+ James Henstridge
154+ Walter Hop
155+ Bert Hubert
156+ Henny Huisman
157+ Jeremy Hylton
158+ Ikeda Soji
159+ Rostyk Ivantsiv
160+ Ron Jarrell
161+ Matthias Juchem
162+ Tamito KAJIYAMA
163+ Nino Katic
164+ SHIGENO Kazutaka
165+ Ashley M. Kirchner
166+ Matthias Klose
167+ Harald Koch
168+ Eddie Kohler
169+ Chris Kolar
170+ Uros Kositer
171+ Andrew Kuchling
172+ Ricardo Kustner
173+ L'homme Moderne
174+ Sylvain Langlade
175+ Ed Lau
176+ J C Lawrence
177+ Greg Lindahl
178+ Christopher P. Lindsey
179+ Martin von Loewis
180+ Dario Lopez-Kästen
181+ Tanner Lovelace
182+ Jay Luker
183+ Gergely Madarasz
184+ Luca Maranzano
185+ John A. Martin
186+ Andrew Martynov
187+ Jason R. Mastaler
188+ Michael Mclay
189+ Michael Meltzer
190+ Marc MERLIN
191+ Nigel Metheringham
192+ Dan Mick
193+ Garey Mills
194+ Martin Mokrejs
195+ Michael Fischer v. Mollard
196+ David MartĂ­nez Moreno
197+ Dirk Mueller
198+ Jonas Muerer
199+ Erik Myllymaki
200+ Balazs Nagy
201+ Moritz Naumann
202+ Dale Newfield
203+ Hrvoje Niksic
204+ Les Niles
205+ Mike Noyes
206+ David B. O'Donnell
207+ Timothy O'Malley
208+ "office"
209+ Dan Ohnesorg
210+ Gerald Oskoboiny
211+ Eva Ă–sterlind
212+ Toni Panadès
213+ Jon Parise
214+ Chris Pepper
215+ Tim Peters
216+ Joe Peterson
217+ PieterB
218+ Rodolfo Pilas
219+ Skye Poier
220+ Martin Pool
221+ Don Porter
222+ Francesco Potortì
223+ Bob Puff
224+ Michael Ranner
225+ John Read
226+ Sean Reifschneider
227+ Christian Reis
228+ Ademar de Souza Reis, Jr.
229+ Bernhard Reiter
230+ Stephan Richter
231+ Tristan Roddis
232+ Heiko Rommel
233+ Luigi Rosa
234+ Guido van Rossum
235+ Nicholas Russo
236+ Chris Ryan
237+ Cabel Sasser
238+ Bartosz Sawicki
239+ Kai Schaetzl
240+ Karoly Segesdi
241+ Gleydson Mazioli da Silva
242+ Pasi Sjöholm
243+ Chris Snell
244+ Mikhail Sobolev
245+ Greg Stein
246+ Dale Stimson
247+ Students of HIT <mailman-cn@mail.cs.hit.edu.cn>
248+ Szabolcs Szigeti
249+ Vizi Szilard
250+ David T-G
251+ Owen Taylor
252+ Danny Terweij
253+ Jim Tittsler
254+ Todd (Freedom Lover)
255+ Roger Tsang
256+ Chuq Von Rospach
257+ Jens Vagelpohl
258+ Valia V. Vaneeva
259+ Anti Veeranna
260+ Todd Vierling
261+ Bill Wagner
262+ Greg Ward
263+ Mark Weaver
264+ Kathleen Webb
265+ Florian Weimer
266+ Ousmane Wilane
267+ Dan Wilder
268+ Seb Wills
269+ Dai Xiaoguang
270+ Ping Yeh
271+ YASUDA Yukihiro
272+ Michael Yount
273+ Blair Zajac
274+ Mikhail Zabaluev
275+ Noam Zeilberger
276+ Daniel Zeiss
277+ Todd Zullinger
278+
279+And everyone else on mailman-developers@python.org and
280+mailman-users@python.org! Thank you, all.
281+
282+
283+
284
285+Local Variables:
286+mode: indented-text
287+indent-tabs-mode: nil
288+End:
289
290=== added file 'BUGS'
291--- BUGS 1970-01-01 00:00:00 +0000
292+++ BUGS 2025-02-01 12:21:11 +0000
293@@ -0,0 +1,13 @@
294+Mailman - The GNU Mailing List Management System
295+Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
296+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
297+
298+The Mailman project is being managed on Launchpad at
299+
300+ https://launchpad.net/mailman
301+
302+You should submit bugs to the Launchpad bug manager at
303+
304+ https://bugs.launchpad.net/mailman
305+
306+If you have a suggested fix, please attach it to your bug report.
307
308=== added file 'FAQ'
309--- FAQ 1970-01-01 00:00:00 +0000
310+++ FAQ 2025-02-01 12:21:11 +0000
311@@ -0,0 +1,383 @@
312+Mailman - The GNU Mailing List Management System
313+Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
314+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
315+
316+Note: We've migrated the FAQ to the wiki at http://wiki.list.org/
317+ To see the Mailman FAQ go to http://wiki.list.org/x/AgA3
318+
319+FREQUENTLY ASKED QUESTIONS
320+
321+Q. How do you spell this program?
322+
323+A. You spell it "Mailman", with a leading capital "M" and a lowercase
324+ second "m". It is incorrect to spell it "MailMan" (i.e. you should
325+ not use StudlyCaps).
326+
327+Q. I'm getting really terrible performance for outgoing messages. It
328+ seems that if the MTA has trouble resolving DNS for any recipients,
329+ qrunner just gets really slow clearing the queue. Any ideas?
330+
331+A. What's likely happening is that your MTA is doing DNS resolution on
332+ recipients for messages delivered locally (i.e. from Mailman to
333+ your MTA via SMTPDirect.py). This is a Bad Thing. You need to
334+ turn off synchronous DNS resolution for messages originating from
335+ the local host.
336+
337+ In Exim, the value to edit is receiver_verify_hosts. Consult the
338+ Mailman Installation Manual for details. Other MTAs have (of
339+ course) different parameters and defaults that control this. First
340+ check the README file for your MTA and then consult your MTA's own
341+ documentation.
342+
343+Q. My list members are complaining about Mailman's List-* headers!
344+ What can I do about this?
345+
346+A. These headers are described in RFC 2369 and are added by Mailman
347+ for the long-term benefit of end-users. While discouraged, the
348+ list admin can disable these via the General Options page. See
349+ also README.USERAGENT for more information.
350+
351+Q. Can I put the user's address in the footer that Mailman adds to
352+ each message?
353+
354+A. Yes, in Mailman 2.1. The site admin needs to enable personalization by
355+ setting the following variable in the mm_cfg.py file:
356+
357+ OWNERS_CAN_ENABLE_PERSONALIZATION = Yes
358+
359+ Once this is done, list admins can enable personalization for regular
360+ delivery members (digest deliveries can't be personalized currently). A
361+ personalized list can include the user's address in the footer.
362+
363+Q. My users hate HTML in their email and for security reasons, I want
364+ to strip out all MIME attachments. How can I do this?
365+
366+A. Mailman 2.1 has this feature built-in. See the Content Filtering
367+ Options page in the admin interface.
368+
369+Q. What if I get "document contains no data" from the web server, or
370+ mail isn't getting delivered, or I see "Premature end of script
371+ headers" or "Mailman CGI error!!!"
372+
373+A. The most likely cause of this is that the GID that is compiled into
374+ the C wrappers does not match the GID that your Web server invokes
375+ CGI scripts with. Note that a similar error could occur if your
376+ mail system invokes filter programs under a GID that does not match
377+ the one compiled into the C mail wrapper.
378+
379+ To fix this you will need to re-configure Mailman using the
380+ --with-cgi-gid and --with-mail-gid options. See the INSTALL file
381+ for details.
382+
383+ These errors are logged to syslog and they do not show up in the
384+ Mailman log files. Problems with the CGI wrapper do get reported
385+ in the web browser though (unless STEALTH_MODE is enabled), and
386+ include the expected GID, so that should help a lot.
387+
388+ You may want to have syslog running and configured to log the
389+ mail.error log class somewhere; on Solaris systems, the line
390+
391+ mail.debug /var/log/syslog
392+
393+ causes the messages to go to them in /var/log/syslog, for example.
394+ (The distributed syslog.conf forwards the message to the loghost,
395+ when present. See the syslog man page for more details.)
396+
397+ If your system is set up like this, and you get a failure trying to
398+ visit the mailman/listinfo web page, and it's due to a UID or GID
399+ mismatch, then you should get an entry at the end of
400+ /var/log/syslog identifying the expected and received values.
401+
402+ If you are not getting any log messages in syslog, or in Mailman's
403+ own log files, but messages are still not being delivered, then it
404+ is likely that qrunner is not running (qrunner is the process that
405+ handles all mail in the system). In Mailman 2.0, qrunner was
406+ invoked from cron so make sure your crontab entries for the
407+ `mailman' user have been installed. In Mailman 2.1, qrunner is
408+ started with the bin/mailmanctl script, which can be invoked
409+ manually, or merged with your OS's init scripts.
410+
411+Q. What should I check periodically?
412+
413+A. Many of the scripts have their standard error logged to
414+ $prefix/logs/error, and some of the modules write caught errors
415+ there, as well, so you should check there at least occasionally to
416+ look for bugs in the code and problems in your setup.
417+
418+ You may want to periodically check the other log files in the logs/
419+ directory, perhaps occasionally rotating them with something like
420+ the Linux logrotate script.
421+
422+Q. I can't access the public archives. Why?
423+
424+A. If you are using Apache, you must make sure that FollowSymLinks is
425+ enabled for the path to the public archives. Note that the actual
426+ archives always reside in the private tree, and only when archives
427+ are public, is the symlink followed. See this archive message for
428+ more details:
429+
430+ http://mail.python.org/pipermail/mailman-users/1998-November/000150.html
431+
432+Q. Still having problems? Running QMail?
433+
434+A. Make sure that you are using "preline" before calling the "mailman"
435+ wrapper:
436+
437+ |preline /home/mailman/mail/mailman post listname
438+
439+ "preline" adds a Unix-style "From " header which the archiver requires.
440+ You can fix the archive mbox files by adding:
441+
442+ From somebody Mon Oct 9 12:27:34 MDT 2000
443+
444+ before every message and re-running the archive command
445+ "bin/arch listname". The archives should now exist.
446+
447+Q. I want to get rid of some messages in my archive. How do I do
448+ this?
449+
450+A. David Rocher posts the following recipe:
451+
452+ * remove $prefix/archives/private/<listname>
453+ * edit $prefix/archives/private/<listname>.mbox/<listname>.mbox [optional]
454+ * run $prefix/bin/arch <listname>
455+
456+Q. How secure are the authentication mechanisms used in Mailman's web
457+ interface?
458+
459+A. If your Mailman installation run on an SSL-enabled web server
460+ (i.e. you access the Mailman web pages with "https://..." URLs),
461+ you should be as safe as SSL itself is.
462+
463+ However, most Mailman installation run under standard,
464+ encryption-unaware servers. There's nothing wrong with that for
465+ most applications, but a sufficiently determined cracker *could*
466+ get unauthorized access by:
467+
468+ * Packet sniffing: The password used to do the initial
469+ authentication for any non-public Mailman page is sent as clear
470+ text over the net. If you consider this to be a big problem, you
471+ really should use an SSL-enabled server.
472+
473+ * Stealing a valid cookie: After successful password
474+ authentication, Mailman sends a "cookie" back to the user's
475+ browser. This cookie will be used for "automatic" authentication
476+ when browsing further within the list's protected pages. Mailman
477+ employs "session cookies" which are set until you quit your
478+ browser or explicitly log out.
479+
480+ Gaining access to the user's cookie (e.g. by being able to read
481+ the user's browser cookie database, or by means of packet
482+ sniffing, or maybe even by some broken browser offering all it's
483+ cookies to any and all sites the user accesses), and at the same
484+ time being able to fulfill the other criteria for using the
485+ cookie could result in unauthorized access.
486+
487+ Note that this problem is more easily exploited when users browse
488+ the web via proxies -- in that case, the cookie would be valid
489+ for any connections made through that proxy, and not just for
490+ connections made from the particular machine the user happens to
491+ be accessing the proxy from.
492+
493+ * Getting access to the user's terminal: This is really just
494+ another kind of cookie stealing. The short cookie expiration
495+ time is supposed to help defeat this problem. It can be
496+ considered the price to pay for the convenience of not having to
497+ type the password in every time.
498+
499+Q. I want to backup my lists. What do I need to save?
500+
501+A. See this FAQ entry: http://wiki.list.org/x/5oA9
502+
503+Q. How do I rename a list?
504+
505+A. Renaming a list is currently a bit of a pain to do completely
506+ correctly, especially if you want to make sure that the old list
507+ contacts are automatically forwarded to the new list. This ought
508+ to be easier. :(
509+
510+ The biggest problem you have is how to stop mail and web traffic to
511+ your list during the transition, and what to do about any mail
512+ undelivered to the old list after the move. I don't think there
513+ are any foolproof steps, but here's how you can reduce the risk:
514+
515+ - Temporarily disable qrunner. To do this, you need to edit the
516+ user `mailman's crontab entry. Execute the following command,
517+ commenting out the qrunner line when you're dropped into your
518+ editor. Then save the file and quit the editor.
519+
520+ % crontab -u mailman -e
521+
522+ - Turn off your mail server. This is mostly harmless since remote
523+ MTAs will just keep retrying until you turn it back on, and it's
524+ not going to be off for very long.
525+
526+ - Next turn off your web server if possible. This of course means
527+ your entire site will be off-line while you make the switch and
528+ this may not be acceptable to you. The next best suggestion is
529+ to set up your permanent redirects now for the list you're
530+ moving. This means that anybody looking for the list under its
531+ old name will be redirected to the new name, but they'll get
532+ errors until you've completed the move.
533+
534+ Let's say the old name is "oldname" and the new name is
535+ "newname". Here are some Apache directives that will do the
536+ trick, though YMMV:
537+
538+ RedirectMatch permanent /mailman/(.*)/oldname(.*) http://www.dom.ain/mailman/$1/newname$2
539+ RedirectMatch permanent /pipermail/oldname(.*) http://www.dom.ain/pipermail/newname$1
540+
541+ Add these to your httpd.conf file and restart Apache.
542+
543+ - Now cd to the directory where you've installed Mailman. Let's
544+ say it's /usr/local/mailman:
545+
546+ % cd /usr/local/mailman
547+
548+ and cd to the `lists' subdirectory:
549+
550+ % cd lists
551+
552+ You should now see the directory `oldname'. Move this to
553+ `newname':
554+
555+ % mv oldname newname
556+
557+ - Now cd to the private archives directory:
558+
559+ % cd ../archives/private
560+
561+ You will need to move the oldname's .mbox directory, and the
562+ .mbox file within that directory. Don't worry about the public
563+ archives; the next few steps will take care of them without
564+ requiring you to fiddle around in the file system:
565+
566+ % mv oldname.mbox newname.mbox
567+ % mv newname.mbox/oldname.mbox newname.mbox/newname.mbox
568+
569+ - You now need to run the `bin/move_list' script to update some of
570+ the internal archiver paths. IMPORTANT: Skip this step if you
571+ are using Mailman 2.1!
572+
573+ % cd ../..
574+ % bin/move_list newname
575+
576+ - You should now regenerate the public archives:
577+
578+ % bin/arch newname
579+
580+ - You'll likely need to change some of your list's configuration
581+ options, especially if you want to accept postings addressed to
582+ the old list on the new list. Visit the admin interface for your
583+ new list:
584+
585+ o Go to the General options
586+
587+ o Change the "real_name" option to reflect the new list's name,
588+ e.g. "Newname"
589+
590+ o Change the subject prefix to reflect the new list's name,
591+ e.g. "[Newname] " (yes, that's a trailing space character).
592+
593+ o Optionally, update other configuration fields like info,
594+ description, or welcome_msg. YMMV.
595+
596+ o Save your changes
597+
598+ o Go to the Privacy options
599+
600+ o Add the old list's address to acceptable_aliases.
601+ E.g. "oldname@dom.ain". This way, (after the /etc/aliases
602+ changes described below) messages posted to the old list will
603+ not be held by the new list for "implicit destination"
604+ approval.
605+
606+ o Save your changes
607+
608+ - Now you want to update your /etc/aliases file to include the
609+ aliases for the new list, and forwards for the old list to the
610+ new list. Note that these instructions are for Sendmail style
611+ alias files, adjust to the specifics of how your MTA is set up.
612+
613+ o Find the lines defining the aliases for your old list's name
614+
615+ o Copy and paste them just below the originals.
616+
617+ o Change all the references of "oldname" to "newname" in the
618+ pasted stanza.
619+
620+ o Now change the targets of the original aliases to forward to
621+ the new aliases. When you're done, you will end up with
622+ /etc/aliases entries like the following (YMMV):
623+
624+ XXX This needs updating for MM2.1!
625+
626+ # Forward the oldname list to the newname list
627+ oldname: newname@dom.ain
628+ oldname-request: newname-request@dom.ain
629+ oldname-admin: newname-admin@dom.ain
630+ oldname-owner: newname-owner@dom.ain
631+
632+ newname: "|/usr/local/mailman/mail/mailman post newname"
633+ newname-admin: "|/usr/local/mailman/mail/mailman mailowner newname"
634+ newname-request: "|/usr/local/mailman/mail/mailman mailcmd newname"
635+ newname-owner: newname-admin
636+
637+ o Run newaliases
638+
639+ - Before you restart everything, you want to make one last check.
640+ You're looking for files in the qfiles/ directory that may have
641+ been addressed to the old list but weren't delivered before you
642+ renamed the list. Do something like the following:
643+
644+ % cd /usr/local/mailman/qfiles
645+ % grep oldname *.msg
646+
647+ If you get no hits, skip to the next step, you've got nothing to
648+ worry about.
649+
650+ If you did get hits, then things get complicated. I warn you
651+ that the rest of this step is untested. :(
652+
653+ For each of the .msg files that were destined for the old list,
654+ you need to change the corresponding .db file. Unfortunately
655+ there's no easy way to do this. Anyway...
656+
657+ Save the following Python code in a file called 'hackdb.py':
658+
659+ -------------------------hackdb.py
660+ import sys
661+ import marshal
662+ fp = open(sys.argv[1])
663+ d = marshal.load(fp)
664+ fp.close()
665+ d['listname'] = sys.argv[2]
666+ fp = open(sys.argv[1], 'w')
667+ marshal.dump(d, fp)
668+ fp.close()
669+ -------------------------
670+
671+ And then for each file that matched your grep above, do the
672+ following:
673+
674+ % python hackdb.py reallylonghexfilenamematch1.db newname
675+
676+ - It's now safe to turn your MTA back on.
677+
678+ - Turn your qrunner back on by running
679+
680+ % crontab -u mailman -e
681+
682+ again and this time uncommenting the qrunner line. Save the file
683+ and quit your editor.
684+
685+ - Rejoice, you're done. Send $100,000 in shiny new pennies to the
686+ Mailman cabal as your downpayment toward making this easier for
687+ the next list you have to rename. :)
688+
689+
690+
691
692+Local Variables:
693+mode: text
694+indent-tabs-mode: nil
695+End:
696
697=== added file 'INSTALL'
698--- INSTALL 1970-01-01 00:00:00 +0000
699+++ INSTALL 2025-02-01 12:21:11 +0000
700@@ -0,0 +1,25 @@
701+Mailman - The GNU Mailing List Management System
702+Copyright (C) 1998-2018 Free Software Foundation, Inc.
703+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
704+
705+The installation and upgrading instructions are now completely contained in
706+the Mailman Installation Guide. Web, PostScript, PDF, and plaintext formats
707+for this guide are available both within this source distribution and online.
708+
709+All manuals within this source distribution are provided in the doc/
710+directory:
711+
712+ HTML : doc/mailman-install/index.html
713+ PostScript : doc/mailman-install.ps
714+ PDF : doc/mailman-install.pdf
715+ plain text : doc/mailman-install.txt
716+
717+Or go online at http://www.list.org/site.html to find the online installation
718+guide.
719+
720+
721+
722
723+Local Variables:
724+mode: indented-text
725+indent-tabs-mode: nil
726+End:
727
728=== added directory 'Mailman'
729=== added directory 'Mailman/Archiver'
730=== added file 'Mailman/Archiver/Archiver.py'
731--- Mailman/Archiver/Archiver.py 1970-01-01 00:00:00 +0000
732+++ Mailman/Archiver/Archiver.py 2025-02-01 12:21:11 +0000
733@@ -0,0 +1,244 @@
734+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
735+#
736+# This program is free software; you can redistribute it and/or
737+# modify it under the terms of the GNU General Public License
738+# as published by the Free Software Foundation; either version 2
739+# of the License, or (at your option) any later version.
740+#
741+# This program is distributed in the hope that it will be useful,
742+# but WITHOUT ANY WARRANTY; without even the implied warranty of
743+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
744+# GNU General Public License for more details.
745+#
746+# You should have received a copy of the GNU General Public License
747+# along with this program; if not, write to the Free Software
748+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
749+
750+
751+"""Mixin class for putting new messages in the right place for archival.
752+
753+Public archives are separated from private ones. An external archival
754+mechanism (eg, pipermail) should be pointed to the right places, to do the
755+archival.
756+"""
757+
758+import os
759+import errno
760+import traceback
761+import re
762+from cStringIO import StringIO
763+
764+from Mailman import mm_cfg
765+from Mailman import Mailbox
766+from Mailman import Utils
767+from Mailman import Site
768+from Mailman.SafeDict import SafeDict
769+from Mailman.Logging.Syslog import syslog
770+from Mailman.i18n import _
771+
772+try:
773+ True, False
774+except NameError:
775+ True = 1
776+ False = 0
777+
778+
779+
780
781+def makelink(old, new):
782+ try:
783+ os.symlink(old, new)
784+ except OSError, e:
785+ if e.errno <> errno.EEXIST:
786+ raise
787+
788+def breaklink(link):
789+ try:
790+ os.unlink(link)
791+ except OSError, e:
792+ if e.errno <> errno.ENOENT:
793+ raise
794+
795+
796+
797
798+class Archiver:
799+ #
800+ # Interface to Pipermail. HyperArch.py uses this method to get the
801+ # archive directory for the mailing list
802+ #
803+ def InitVars(self):
804+ # Configurable
805+ self.archive = mm_cfg.DEFAULT_ARCHIVE
806+ # 0=public, 1=private:
807+ self.archive_private = mm_cfg.DEFAULT_ARCHIVE_PRIVATE
808+ self.archive_volume_frequency = \
809+ mm_cfg.DEFAULT_ARCHIVE_VOLUME_FREQUENCY
810+ # The archive file structure by default is:
811+ #
812+ # archives/
813+ # private/
814+ # listname.mbox/
815+ # listname.mbox
816+ # listname/
817+ # lots-of-pipermail-stuff
818+ # public/
819+ # listname.mbox@ -> ../private/listname.mbox
820+ # listname@ -> ../private/listname
821+ #
822+ # IOW, the mbox and pipermail archives are always stored in the
823+ # private archive for the list. This is safe because archives/private
824+ # is always set to o-rx. Public archives have a symlink to get around
825+ # the private directory, pointing directly to the private/listname
826+ # which has o+rx permissions. Private archives do not have the
827+ # symbolic links.
828+ omask = os.umask(0)
829+ try:
830+ try:
831+ os.mkdir(self.archive_dir()+'.mbox', 02775)
832+ except OSError, e:
833+ if e.errno <> errno.EEXIST: raise
834+ # We also create an empty pipermail archive directory into
835+ # which we'll drop an empty index.html file into. This is so
836+ # that lists that have not yet received a posting have
837+ # /something/ as their index.html, and don't just get a 404.
838+ try:
839+ os.mkdir(self.archive_dir(), 02775)
840+ except OSError, e:
841+ if e.errno <> errno.EEXIST: raise
842+ # See if there's an index.html file there already and if not,
843+ # write in the empty archive notice.
844+ indexfile = os.path.join(self.archive_dir(), 'index.html')
845+ fp = None
846+ try:
847+ fp = open(indexfile)
848+ except IOError, e:
849+ if e.errno <> errno.ENOENT: raise
850+ omask = os.umask(002)
851+ try:
852+ fp = open(indexfile, 'w')
853+ finally:
854+ os.umask(omask)
855+ fp.write(Utils.maketext(
856+ 'emptyarchive.html',
857+ {'listname': self.real_name,
858+ 'listinfo': self.GetScriptURL('listinfo', absolute=1),
859+ }, mlist=self))
860+ if fp:
861+ fp.close()
862+ finally:
863+ os.umask(omask)
864+
865+ def archive_dir(self):
866+ return Site.get_archpath(self.internal_name())
867+
868+ def ArchiveFileName(self):
869+ """The mbox name where messages are left for archive construction."""
870+ return os.path.join(self.archive_dir() + '.mbox',
871+ self.internal_name() + '.mbox')
872+
873+ def GetBaseArchiveURL(self):
874+ url = self.GetScriptURL('private', absolute=1) + '/'
875+ if self.archive_private:
876+ return url
877+ else:
878+ hostname = re.match('[^:]*://([^/]*)/.*', url).group(1)\
879+ or mm_cfg.DEFAULT_URL_HOST
880+ url = mm_cfg.PUBLIC_ARCHIVE_URL % {
881+ 'listname': self.internal_name(),
882+ 'hostname': hostname
883+ }
884+ if not url.endswith('/'):
885+ url += '/'
886+ return url
887+
888+ def __archive_file(self, afn):
889+ """Open (creating, if necessary) the named archive file."""
890+ omask = os.umask(002)
891+ try:
892+ return Mailbox.Mailbox(open(afn, 'a+'))
893+ finally:
894+ os.umask(omask)
895+
896+ #
897+ # old ArchiveMail function, retained under a new name
898+ # for optional archiving to an mbox
899+ #
900+ def __archive_to_mbox(self, post):
901+ """Retain a text copy of the message in an mbox file."""
902+ try:
903+ afn = self.ArchiveFileName()
904+ mbox = self.__archive_file(afn)
905+ mbox.AppendMessage(post)
906+ mbox.fp.close()
907+ except IOError, msg:
908+ syslog('error', 'Archive file access failure:\n\t%s %s', afn, msg)
909+ raise
910+
911+ def ExternalArchive(self, ar, txt):
912+ d = SafeDict({'listname': self.internal_name(),
913+ 'hostname': self.host_name,
914+ })
915+ cmd = ar % d
916+ extarch = os.popen(cmd, 'w')
917+ extarch.write(txt)
918+ status = extarch.close()
919+ if status:
920+ syslog('error', 'external archiver non-zero exit status: %d\n',
921+ (status & 0xff00) >> 8)
922+
923+ #
924+ # archiving in real time this is called from list.post(msg)
925+ #
926+ def ArchiveMail(self, msg):
927+ """Store postings in mbox and/or pipermail archive, depending."""
928+ # Fork so archival errors won't disrupt normal list delivery
929+ if mm_cfg.ARCHIVE_TO_MBOX == -1:
930+ return
931+ #
932+ # We don't need an extra archiver lock here because we know the list
933+ # itself must be locked.
934+ if mm_cfg.ARCHIVE_TO_MBOX in (1, 2):
935+ self.__archive_to_mbox(msg)
936+ if mm_cfg.ARCHIVE_TO_MBOX == 1:
937+ # Archive to mbox only.
938+ return
939+ txt = str(msg)
940+ # should we use the internal or external archiver?
941+ private_p = self.archive_private
942+ if mm_cfg.PUBLIC_EXTERNAL_ARCHIVER and not private_p:
943+ self.ExternalArchive(mm_cfg.PUBLIC_EXTERNAL_ARCHIVER, txt)
944+ elif mm_cfg.PRIVATE_EXTERNAL_ARCHIVER and private_p:
945+ self.ExternalArchive(mm_cfg.PRIVATE_EXTERNAL_ARCHIVER, txt)
946+ else:
947+ # use the internal archiver
948+ f = StringIO(txt)
949+ import HyperArch
950+ h = HyperArch.HyperArchive(self)
951+ h.processUnixMailbox(f)
952+ h.close()
953+ f.close()
954+
955+ #
956+ # called from MailList.MailList.Save()
957+ #
958+ def CheckHTMLArchiveDir(self):
959+ # We need to make sure that the archive directory has the right perms
960+ # for public vs private. If it doesn't exist, or some weird
961+ # permissions errors prevent us from stating the directory, it's
962+ # pointless to try to fix the perms, so we just return -scott
963+ if mm_cfg.ARCHIVE_TO_MBOX == -1:
964+ # Archiving is completely disabled, don't require the skeleton.
965+ return
966+ pubdir = Site.get_archpath(self.internal_name(), public=True)
967+ privdir = self.archive_dir()
968+ pubmbox = pubdir + '.mbox'
969+ privmbox = privdir + '.mbox'
970+ if self.archive_private:
971+ breaklink(pubdir)
972+ breaklink(pubmbox)
973+ else:
974+ # BAW: privdir or privmbox could be nonexistant. We'd get an
975+ # OSError, ENOENT which should be caught and reported properly.
976+ makelink(privdir, pubdir)
977+ # Only make this link if the site has enabled public mbox files
978+ if mm_cfg.PUBLIC_MBOX:
979+ makelink(privmbox, pubmbox)
980
981=== added file 'Mailman/Archiver/HyperArch.py'
982--- Mailman/Archiver/HyperArch.py 1970-01-01 00:00:00 +0000
983+++ Mailman/Archiver/HyperArch.py 2025-02-01 12:21:11 +0000
984@@ -0,0 +1,1335 @@
985+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
986+#
987+# This program is free software; you can redistribute it and/or
988+# modify it under the terms of the GNU General Public License
989+# as published by the Free Software Foundation; either version 2
990+# of the License, or (at your option) any later version.
991+#
992+# This program is distributed in the hope that it will be useful,
993+# but WITHOUT ANY WARRANTY; without even the implied warranty of
994+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
995+# GNU General Public License for more details.
996+#
997+# You should have received a copy of the GNU General Public License
998+# along with this program; if not, write to the Free Software
999+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
1000+# USA.
1001+
1002+"""HyperArch: Pipermail archiving for Mailman
1003+
1004+ - The Dragon De Monsyne <dragondm@integral.org>
1005+
1006+ TODO:
1007+ - Should be able to force all HTML to be regenerated next time the
1008+ archive is run, in case a template is changed.
1009+ - Run a command to generate tarball of html archives for downloading
1010+ (probably in the 'update_dirty_archives' method).
1011+"""
1012+
1013+from __future__ import nested_scopes
1014+
1015+import sys
1016+import re
1017+import errno
1018+import urllib
1019+import time
1020+import os
1021+import types
1022+import HyperDatabase
1023+import pipermail
1024+import weakref
1025+import binascii
1026+
1027+from email.Header import decode_header, make_header
1028+from email.Errors import HeaderParseError
1029+from email.Charset import Charset
1030+
1031+from Mailman import mm_cfg
1032+from Mailman import Utils
1033+from Mailman import Errors
1034+from Mailman import LockFile
1035+from Mailman import MailList
1036+from Mailman import i18n
1037+from Mailman.SafeDict import SafeDict
1038+from Mailman.Logging.Syslog import syslog
1039+from Mailman.Mailbox import ArchiverMailbox
1040+
1041+# Set up i18n. Assume the current language has already been set in the caller.
1042+_ = i18n._
1043+C_ = i18n.C_
1044+
1045+gzip = None
1046+if mm_cfg.GZIP_ARCHIVE_TXT_FILES:
1047+ try:
1048+ import gzip
1049+ except ImportError:
1050+ pass
1051+
1052+EMPTYSTRING = ''
1053+NL = '\n'
1054+
1055+# MacOSX has a default stack size that is too small for deeply recursive
1056+# regular expressions. We see this as crashes in the Python test suite when
1057+# running test_re.py and test_sre.py. The fix is to set the stack limit to
1058+# 2048; the general recommendation is to do in the shell before running the
1059+# test suite. But that's inconvenient for a daemon like the qrunner.
1060+#
1061+# AFAIK, this problem only affects the archiver, so we're adding this work
1062+# around to this file (it'll get imported by the bundled pipermail or by the
1063+# bin/arch script. We also only do this on darwin, a.k.a. MacOSX.
1064+if sys.platform == 'darwin':
1065+ try:
1066+ import resource
1067+ except ImportError:
1068+ pass
1069+ else:
1070+ soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
1071+ newsoft = min(hard, max(soft, 1024*2048))
1072+ resource.setrlimit(resource.RLIMIT_STACK, (newsoft, hard))
1073+
1074+
1075+try:
1076+ True, False
1077+except NameError:
1078+ True = 1
1079+ False = 0
1080+
1081+
1082+
1083
1084+def html_quote(s, lang=None):
1085+ repls = ( ('&', '&amp;'),
1086+ ("<", '&lt;'),
1087+ (">", '&gt;'),
1088+ ('"', '&quot;'))
1089+ for thing, repl in repls:
1090+ s = s.replace(thing, repl)
1091+ return Utils.uncanonstr(s, lang)
1092+
1093+
1094+def url_quote(s):
1095+ return urllib.quote(s)
1096+
1097+
1098+def null_to_space(s):
1099+ return s.replace('\000', ' ')
1100+
1101+
1102+def sizeof(filename, lang):
1103+ try:
1104+ size = os.path.getsize(filename)
1105+ except OSError, e:
1106+ # ENOENT can happen if the .mbox file was moved away or deleted, and
1107+ # an explicit mbox file name was given to bin/arch.
1108+ if e.errno <> errno.ENOENT: raise
1109+ return _('size not available')
1110+ if size < 1000:
1111+ # Avoid i18n side-effects
1112+ otrans = i18n.get_translation()
1113+ try:
1114+ i18n.set_language(lang)
1115+ out = _(' %(size)i bytes ')
1116+ finally:
1117+ i18n.set_translation(otrans)
1118+ return out
1119+ elif size < 1000000:
1120+ return ' %d KB ' % (size / 1000)
1121+ # GB?? :-)
1122+ return ' %d MB ' % (size / 1000000)
1123+
1124+
1125+html_charset = '<META http-equiv="Content-Type" ' \
1126+ 'content="text/html; charset=%s">'
1127+
1128+def CGIescape(arg, lang=None):
1129+ if isinstance(arg, types.UnicodeType):
1130+ s = Utils.websafe(arg)
1131+ else:
1132+ s = Utils.websafe(str(arg))
1133+ return Utils.uncanonstr(s.replace('"', '&quot;'), lang)
1134+
1135+# Parenthesized human name
1136+paren_name_pat = re.compile(r'([(].*[)])')
1137+
1138+# Subject lines preceded with 'Re:'
1139+REpat = re.compile( r"\s*RE\s*(\[\d+\]\s*)?:\s*", re.IGNORECASE)
1140+
1141+# E-mail addresses and URLs in text
1142+emailpat = re.compile(r'([-+,.\w]+@[-+.\w]+)')
1143+
1144+# Argh! This pattern is buggy, and will choke on URLs with GET parameters.
1145+# MAS: Given that people are not constrained in how they write URIs in plain
1146+# text, it is not possible to have a single regexp to reliably match them.
1147+# The regexp below is intended to match straightforward cases. Even humans
1148+# can't reliably tell whether various punctuation at the end of a URI is part
1149+# of the URI or not.
1150+urlpat = re.compile(r'([a-z]+://.*?)(?:_\s|_$|$|[]})>\'"\s])', re.IGNORECASE)
1151+
1152+# Blank lines
1153+blankpat = re.compile(r'^\s*$')
1154+
1155+# Starting <html> directive
1156+htmlpat = re.compile(r'^\s*<HTML>\s*$', re.IGNORECASE)
1157+# Ending </html> directive
1158+nohtmlpat = re.compile(r'^\s*</HTML>\s*$', re.IGNORECASE)
1159+# Match quoted text
1160+quotedpat = re.compile(r'^([>|:]|&gt;)+')
1161+
1162+
1163+
1164
1165+# Like Utils.maketext() but with caching to improve performance.
1166+#
1167+# _templatefilepathcache is used to associate a (templatefile, lang, listname)
1168+# key with the file system path to a template file. This path is the one that
1169+# the Utils.findtext() function has computed is the one to match the values in
1170+# the key tuple.
1171+#
1172+# _templatecache associate a file system path as key with the text
1173+# returned after processing the contents of that file by Utils.findtext()
1174+#
1175+# We keep two caches to reduce the amount of template text kept in memory,
1176+# since the _templatefilepathcache is a many->one mapping and _templatecache
1177+# is a one->one mapping. Imagine 1000 lists all using the same default
1178+# English template.
1179+
1180+_templatefilepathcache = {}
1181+_templatecache = {}
1182+
1183+def quick_maketext(templatefile, dict=None, lang=None, mlist=None):
1184+ if mlist is None:
1185+ listname = ''
1186+ else:
1187+ listname = mlist._internal_name
1188+ if lang is None:
1189+ if mlist is None:
1190+ lang = mm_cfg.DEFAULT_SERVER_LANGUAGE
1191+ else:
1192+ lang = mlist.preferred_language
1193+ cachekey = (templatefile, lang, listname)
1194+ filepath = _templatefilepathcache.get(cachekey)
1195+ if filepath:
1196+ template = _templatecache.get(filepath)
1197+ if filepath is None or template is None:
1198+ # Use the basic maketext, with defaults to get the raw template
1199+ template, filepath = Utils.findtext(templatefile, lang=lang,
1200+ raw=True, mlist=mlist)
1201+ _templatefilepathcache[cachekey] = filepath
1202+ _templatecache[filepath] = template
1203+ # Copied from Utils.maketext()
1204+ text = template
1205+ if dict is not None:
1206+ try:
1207+ sdict = SafeDict(dict)
1208+ try:
1209+ text = sdict.interpolate(template)
1210+ except UnicodeError:
1211+ # Try again after coercing the template to unicode
1212+ utemplate = unicode(template,
1213+ Utils.GetCharSet(lang),
1214+ 'replace')
1215+ text = sdict.interpolate(utemplate)
1216+ except (TypeError, ValueError), e:
1217+ # The template is really screwed up
1218+ syslog('error', 'broken template: %s\n%s', filepath, e)
1219+ # Make sure the text is in the given character set, or html-ify any bogus
1220+ # characters.
1221+ return Utils.uncanonstr(text, lang)
1222+
1223+
1224+
1225
1226+# Note: I'm overriding most, if not all of the pipermail Article class
1227+# here -ddm
1228+# The Article class encapsulates a single posting. The attributes are:
1229+#
1230+# sequence : Sequence number, unique for each article in a set of archives
1231+# subject : Subject
1232+# datestr : The posting date, in human-readable format
1233+# date : The posting date, in purely numeric format
1234+# fromdate : The posting date, in `unixfrom' format
1235+# headers : Any other headers of interest
1236+# author : The author's name (and possibly organization)
1237+# email : The author's e-mail address
1238+# msgid : A unique message ID
1239+# in_reply_to : If !="", this is the msgid of the article being replied to
1240+# references: A (possibly empty) list of msgid's of earlier articles in
1241+# the thread
1242+# body : A list of strings making up the message body
1243+
1244+class Article(pipermail.Article):
1245+ __super_init = pipermail.Article.__init__
1246+ __super_set_date = pipermail.Article._set_date
1247+
1248+ _last_article_time = time.time()
1249+
1250+ def __init__(self, message=None, sequence=0, keepHeaders=[],
1251+ lang=mm_cfg.DEFAULT_SERVER_LANGUAGE, mlist=None):
1252+ self.__super_init(message, sequence, keepHeaders)
1253+ self.prev = None
1254+ self.next = None
1255+ # Trim Re: from the subject line
1256+ i = 0
1257+ while i != -1:
1258+ result = REpat.match(self.subject)
1259+ if result:
1260+ i = result.end(0)
1261+ self.subject = self.subject[i:]
1262+ if self.subject == '':
1263+ self.subject = _('No subject')
1264+ else:
1265+ i = -1
1266+ # Useful to keep around
1267+ self._lang = lang
1268+ self._mlist = mlist
1269+
1270+ if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS:
1271+ # Avoid i18n side-effects. Note that the language for this
1272+ # article (for this list) could be different from the site-wide
1273+ # preferred language, so we need to ensure no side-effects will
1274+ # occur. Think what happens when executing bin/arch.
1275+ otrans = i18n.get_translation()
1276+ try:
1277+ i18n.set_language(lang)
1278+ if self.author == self.email:
1279+ self.author = self.email = re.sub('@', _(' at '),
1280+ self.email)
1281+ else:
1282+ self.email = re.sub('@', _(' at '), self.email)
1283+ finally:
1284+ i18n.set_translation(otrans)
1285+
1286+ # Snag the content-* headers. RFC 1521 states that their values are
1287+ # case insensitive.
1288+ ctype = message.get('Content-Type', 'text/plain')
1289+ cenc = message.get('Content-Transfer-Encoding', '')
1290+ self.ctype = ctype.lower()
1291+ self.cenc = cenc.lower()
1292+ self.decoded = {}
1293+ cset = Utils.GetCharSet(mlist.preferred_language)
1294+ cset_out = Charset(cset).output_charset or cset
1295+ if isinstance(cset_out, unicode):
1296+ # email 3.0.1 (python 2.4) doesn't like unicode
1297+ cset_out = cset_out.encode('us-ascii')
1298+ charset = message.get_content_charset(cset_out)
1299+ if charset:
1300+ charset = charset.lower().strip()
1301+ if charset[0]=='"' and charset[-1]=='"':
1302+ charset = charset[1:-1]
1303+ if charset[0]=="'" and charset[-1]=="'":
1304+ charset = charset[1:-1]
1305+ try:
1306+ body = message.get_payload(decode=True)
1307+ except binascii.Error:
1308+ body = None
1309+ if body and charset != Utils.GetCharSet(self._lang):
1310+ # decode body
1311+ try:
1312+ body = unicode(body, charset)
1313+ except (UnicodeError, LookupError):
1314+ body = None
1315+ if body:
1316+ self.body = [l + "\n" for l in body.splitlines()]
1317+
1318+ self.decode_headers()
1319+
1320+ # Mapping of listnames to MailList instances as a weak value dictionary.
1321+ # This code is copied from Runner.py but there's one important operational
1322+ # difference. In Runner.py, we always .Load() the MailList object for
1323+ # each _dispose() run, otherwise the object retrieved from the cache won't
1324+ # be up-to-date. Since we're creating a new HyperArchive instance for
1325+ # each message being archived, we don't need to worry about that -- but it
1326+ # does mean there are additional opportunities for optimization.
1327+ _listcache = weakref.WeakValueDictionary()
1328+
1329+ def _open_list(self, listname):
1330+ # Cache the open list so that any use of the list within this process
1331+ # uses the same object. We use a WeakValueDictionary so that when the
1332+ # list is no longer necessary, its memory is freed.
1333+ mlist = self._listcache.get(listname)
1334+ if not mlist:
1335+ try:
1336+ mlist = MailList.MailList(listname, lock=0)
1337+ except Errors.MMListError, e:
1338+ syslog('error', 'error opening list: %s\n%s', listname, e)
1339+ return None
1340+ else:
1341+ self._listcache[listname] = mlist
1342+ return mlist
1343+
1344+ def __getstate__(self):
1345+ d = self.__dict__.copy()
1346+ # We definitely don't want to pickle the MailList instance, so just
1347+ # pickle a reference to it.
1348+ if d.has_key('_mlist'):
1349+ mlist = d['_mlist']
1350+ del d['_mlist']
1351+ else:
1352+ mlist = None
1353+ if mlist:
1354+ d['__listname'] = self._mlist.internal_name()
1355+ else:
1356+ d['__listname'] = None
1357+ # Delete a few other things we don't want in the pickle
1358+ for attr in ('prev', 'next', 'body'):
1359+ if d.has_key(attr):
1360+ del d[attr]
1361+ d['body'] = []
1362+ return d
1363+
1364+ def __setstate__(self, d):
1365+ # For loading older Articles via pickle. All this stuff was added
1366+ # when Simone Piunni and Tokio Kikuchi i18n'ified Pipermail. See SF
1367+ # patch #594771.
1368+ self.__dict__ = d
1369+ listname = d.get('__listname')
1370+ if listname:
1371+ del d['__listname']
1372+ d['_mlist'] = self._open_list(listname)
1373+ if not d.has_key('_lang'):
1374+ if hasattr(self, '_mlist'):
1375+ self._lang = self._mlist.preferred_language
1376+ else:
1377+ self._lang = mm_cfg.DEFAULT_SERVER_LANGUAGE
1378+ if not d.has_key('cenc'):
1379+ self.cenc = None
1380+ if not d.has_key('decoded'):
1381+ self.decoded = {}
1382+
1383+ def setListIfUnset(self, mlist):
1384+ if getattr(self, '_mlist', None) is None:
1385+ self._mlist = mlist
1386+
1387+ def quote(self, buf):
1388+ return html_quote(buf, self._lang)
1389+
1390+ def decode_headers(self):
1391+ """MIME-decode headers.
1392+
1393+ If the email, subject, or author attributes contain non-ASCII
1394+ characters using the encoded-word syntax of RFC 2047, decoded versions
1395+ of those attributes are placed in the self.decoded (a dictionary).
1396+
1397+ If the list's charset differs from the header charset, an attempt is
1398+ made to decode the headers as Unicode. If that fails, they are left
1399+ undecoded.
1400+ """
1401+ author = self.decode_charset(self.author)
1402+ subject = self.decode_charset(self.subject)
1403+ if author:
1404+ self.decoded['author'] = author
1405+ email = self.decode_charset(self.email)
1406+ if email:
1407+ self.decoded['email'] = email
1408+ if subject:
1409+ if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS:
1410+ otrans = i18n.get_translation()
1411+ try:
1412+ i18n.set_language(self._lang)
1413+ atmark = unicode(_(' at '), Utils.GetCharSet(self._lang))
1414+ subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)',
1415+ '\g<1>' + atmark + '\g<2>', subject)
1416+ finally:
1417+ i18n.set_translation(otrans)
1418+ self.decoded['subject'] = subject
1419+ self.decoded['stripped'] = self.strip_subject(subject or self.subject)
1420+
1421+ def strip_subject(self, subject):
1422+ # Strip subject_prefix and Re: for subject sorting
1423+ # This part was taken from CookHeaders.py (TK)
1424+ prefix = self._mlist.subject_prefix.strip()
1425+ if prefix:
1426+ prefix_pat = re.escape(prefix)
1427+ prefix_pat = '%'.join(prefix_pat.split(r'\%'))
1428+ prefix_pat = re.sub(r'%\d*d', r'\s*\d+\s*', prefix_pat)
1429+ subject = re.sub(prefix_pat, '', subject)
1430+ subject = subject.lstrip()
1431+ # MAS Should we strip FW and FWD too?
1432+ strip_pat = re.compile('^((RE|AW|SV|VS)(\[\d+\])?:\s*)+', re.I)
1433+ stripped = strip_pat.sub('', subject)
1434+ # Also remove whitespace to avoid folding/unfolding differences
1435+ stripped = re.sub('\s', '', stripped)
1436+ return stripped
1437+
1438+ def decode_charset(self, field):
1439+ # TK: This function was rewritten for unifying to Unicode.
1440+ # Convert 'field' into Unicode one line string.
1441+ try:
1442+ pairs = decode_header(field)
1443+ ustr = make_header(pairs).__unicode__()
1444+ except (LookupError, UnicodeError, ValueError, HeaderParseError):
1445+ # assume list's language
1446+ cset = Utils.GetCharSet(self._mlist.preferred_language)
1447+ if cset == 'us-ascii':
1448+ cset = 'iso-8859-1' # assume this for English list
1449+ ustr = unicode(field, cset, 'replace')
1450+ return u''.join(ustr.splitlines())
1451+
1452+ def as_html(self):
1453+ d = self.__dict__.copy()
1454+ # avoid i18n side-effects
1455+ otrans = i18n.get_translation()
1456+ i18n.set_language(self._lang)
1457+ try:
1458+ d["prev"], d["prev_wsubj"] = self._get_prev()
1459+ d["next"], d["next_wsubj"] = self._get_next()
1460+
1461+ d["email_html"] = self.quote(self.email)
1462+ d["title"] = self.quote(self.subject)
1463+ d["subject_html"] = self.quote(self.subject)
1464+ d["message_id"] = self.quote(self._message_id)
1465+ # TK: These two _url variables are used to compose a response
1466+ # from the archive web page. So, ...
1467+ d["subject_url"] = url_quote('Re: ' + self.subject)
1468+ d["in_reply_to_url"] = url_quote(self._message_id)
1469+ if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS:
1470+ # Point the mailto url back to the list
1471+ author = re.sub('@', _(' at '), self.author)
1472+ emailurl = self._mlist.GetListEmail()
1473+ else:
1474+ author = self.author
1475+ emailurl = self.email
1476+ d["author_html"] = self.quote(author)
1477+ d["email_url"] = url_quote(emailurl)
1478+ d["datestr_html"] = self.quote(i18n.ctime(int(self.date)))
1479+ d["body"] = self._get_body()
1480+ d['listurl'] = self._mlist.GetScriptURL('listinfo', absolute=1)
1481+ d['listname'] = self._mlist.real_name
1482+ d['encoding'] = ''
1483+ finally:
1484+ i18n.set_translation(otrans)
1485+
1486+ charset = Utils.GetCharSet(self._lang)
1487+ d["encoding"] = html_charset % charset
1488+
1489+ self._add_decoded(d)
1490+ return quick_maketext(
1491+ 'article.html', d,
1492+ lang=self._lang, mlist=self._mlist)
1493+
1494+ def _get_prev(self):
1495+ """Return the href and subject for the previous message"""
1496+ if self.prev:
1497+ subject = self._get_subject_enc(self.prev)
1498+ prev = ('<LINK REL="Previous" HREF="%s">'
1499+ % (url_quote(self.prev.filename)))
1500+ prev_wsubj = ('<LI>' + _('Previous message (by thread):') +
1501+ ' <A HREF="%s">%s\n</A></li>'
1502+ % (url_quote(self.prev.filename),
1503+ self.quote(subject)))
1504+ else:
1505+ prev = prev_wsubj = ""
1506+ return prev, prev_wsubj
1507+
1508+ def _get_subject_enc(self, art):
1509+ """Return the subject of art, decoded if possible.
1510+
1511+ If the charset of the current message and art match and the
1512+ article's subject is encoded, decode it.
1513+ """
1514+ return art.decoded.get('subject', art.subject)
1515+
1516+ def _get_next(self):
1517+ """Return the href and subject for the previous message"""
1518+ if self.next:
1519+ subject = self._get_subject_enc(self.next)
1520+ next = ('<LINK REL="Next" HREF="%s">'
1521+ % (url_quote(self.next.filename)))
1522+ next_wsubj = ('<LI>' + _('Next message (by thread):') +
1523+ ' <A HREF="%s">%s\n</A></li>'
1524+ % (url_quote(self.next.filename),
1525+ self.quote(subject)))
1526+ else:
1527+ next = next_wsubj = ""
1528+ return next, next_wsubj
1529+
1530+ _rx_quote = re.compile('=([A-F0-9][A-F0-9])')
1531+ _rx_softline = re.compile('=[ \t]*$')
1532+
1533+ def _get_body(self):
1534+ """Return the message body ready for HTML, decoded if necessary"""
1535+ try:
1536+ body = self.html_body
1537+ except AttributeError:
1538+ body = self.body
1539+ return null_to_space(EMPTYSTRING.join(body))
1540+
1541+ def _add_decoded(self, d):
1542+ """Add encoded-word keys to HTML output"""
1543+ for src, dst in (('author', 'author_html'),
1544+ ('email', 'email_html'),
1545+ ('subject', 'subject_html'),
1546+ ('subject', 'title')):
1547+ if self.decoded.has_key(src):
1548+ d[dst] = self.quote(self.decoded[src])
1549+
1550+ def as_text(self):
1551+ d = self.__dict__.copy()
1552+ # We need to guarantee a valid From_ line, even if there are
1553+ # bososities in the headers.
1554+ if not d.get('fromdate', '').strip():
1555+ d['fromdate'] = time.ctime(time.time())
1556+ if not d.get('email', '').strip():
1557+ d['email'] = 'bogus@does.not.exist.com'
1558+ if not d.get('datestr', '').strip():
1559+ d['datestr'] = time.ctime(time.time())
1560+ #
1561+ headers = ['From %(email)s %(fromdate)s',
1562+ 'From: %(email)s (%(author)s)',
1563+ 'Date: %(datestr)s',
1564+ 'Subject: %(subject)s']
1565+ if d['_in_reply_to']:
1566+ headers.append('In-Reply-To: %(_in_reply_to)s')
1567+ if d['_references']:
1568+ headers.append('References: %(_references)s')
1569+ if d['_message_id']:
1570+ headers.append('Message-ID: %(_message_id)s')
1571+ body = EMPTYSTRING.join(self.body)
1572+ cset = Utils.GetCharSet(self._lang)
1573+ # Coerce the body to Unicode and replace any invalid characters.
1574+ if not isinstance(body, types.UnicodeType):
1575+ body = unicode(body, cset, 'replace')
1576+ if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS:
1577+ otrans = i18n.get_translation()
1578+ try:
1579+ i18n.set_language(self._lang)
1580+ atmark = unicode(_(' at '), cset)
1581+ body = re.sub(r'([-+,.\w]+)@([-+.\w]+)',
1582+ '\g<1>' + atmark + '\g<2>', body)
1583+ finally:
1584+ i18n.set_translation(otrans)
1585+ # Return body to character set of article.
1586+ body = body.encode(cset, 'replace')
1587+ return NL.join(headers) % d + '\n\n' + body + '\n'
1588+
1589+ def _set_date(self, message):
1590+ self.__super_set_date(message)
1591+ self.fromdate = time.ctime(int(self.date))
1592+
1593+ def loadbody_fromHTML(self,fileobj):
1594+ self.body = []
1595+ begin = 0
1596+ while 1:
1597+ line = fileobj.readline()
1598+ if not line:
1599+ break
1600+ if not begin:
1601+ if line.strip() == '<!--beginarticle-->':
1602+ begin = 1
1603+ continue
1604+ if line.strip() == '<!--endarticle-->':
1605+ break
1606+ self.body.append(line)
1607+
1608+ def finished_update_article(self):
1609+ self.body = []
1610+ try:
1611+ del self.html_body
1612+ except AttributeError:
1613+ pass
1614+
1615+
1616
1617+class HyperArchive(pipermail.T):
1618+ __super_init = pipermail.T.__init__
1619+ __super_update_archive = pipermail.T.update_archive
1620+ __super_update_dirty_archives = pipermail.T.update_dirty_archives
1621+ __super_add_article = pipermail.T.add_article
1622+
1623+ # some defaults
1624+ DIRMODE = 02775
1625+ FILEMODE = 0660
1626+
1627+ VERBOSE = 0
1628+ DEFAULTINDEX = 'thread'
1629+ ARCHIVE_PERIOD = 'month'
1630+
1631+ THREADLAZY = 0
1632+ THREADLEVELS = 3
1633+
1634+ ALLOWHTML = 1 # "Lines between <html></html>" handled as is.
1635+ SHOWHTML = 0 # Eg, nuke leading whitespace in html manner.
1636+ IQUOTES = 1 # Italicize quoted text.
1637+ SHOWBR = 0 # Add <br> onto every line
1638+
1639+ def __init__(self, maillist):
1640+ # can't init the database while other processes are writing to it!
1641+ # XXX TODO- implement native locking
1642+ # with mailman's LockFile module for HyperDatabase.HyperDatabase
1643+ #
1644+ dir = maillist.archive_dir()
1645+ db = HyperDatabase.HyperDatabase(dir, maillist)
1646+ self.__super_init(dir, reload=1, database=db)
1647+
1648+ self.maillist = maillist
1649+ self._lock_file = None
1650+ self.lang = maillist.preferred_language
1651+ self.charset = Utils.GetCharSet(maillist.preferred_language)
1652+
1653+ if hasattr(self.maillist,'archive_volume_frequency'):
1654+ if self.maillist.archive_volume_frequency == 0:
1655+ self.ARCHIVE_PERIOD='year'
1656+ elif self.maillist.archive_volume_frequency == 2:
1657+ self.ARCHIVE_PERIOD='quarter'
1658+ elif self.maillist.archive_volume_frequency == 3:
1659+ self.ARCHIVE_PERIOD='week'
1660+ elif self.maillist.archive_volume_frequency == 4:
1661+ self.ARCHIVE_PERIOD='day'
1662+ else:
1663+ self.ARCHIVE_PERIOD='month'
1664+
1665+ yre = r'(?P<year>[0-9]{4,4})'
1666+ mre = r'(?P<month>[01][0-9])'
1667+ dre = r'(?P<day>[0123][0-9])'
1668+ self._volre = {
1669+ 'year': '^' + yre + '$',
1670+ 'quarter': '^' + yre + r'q(?P<quarter>[1234])$',
1671+ 'month': '^' + yre + r'-(?P<month>[a-zA-Z]+)$',
1672+ 'week': r'^Week-of-Mon-' + yre + mre + dre,
1673+ 'day': '^' + yre + mre + dre + '$'
1674+ }
1675+
1676+ def _makeArticle(self, msg, sequence):
1677+ return Article(msg, sequence,
1678+ lang=self.maillist.preferred_language,
1679+ mlist=self.maillist)
1680+
1681+ def html_foot(self):
1682+ # avoid i18n side-effects
1683+ mlist = self.maillist
1684+ otrans = i18n.get_translation()
1685+ i18n.set_language(mlist.preferred_language)
1686+ # Convenience
1687+ def quotetime(s):
1688+ return html_quote(i18n.ctime(s), self.lang)
1689+ try:
1690+ d = {"lastdate": quotetime(self.lastdate),
1691+ "archivedate": quotetime(self.archivedate),
1692+ "listinfo": mlist.GetScriptURL('listinfo', absolute=1),
1693+ "version": self.version,
1694+ "listname": html_quote(mlist.real_name, self.lang),
1695+ }
1696+ i = {"thread": _("thread"),
1697+ "subject": _("subject"),
1698+ "author": _("author"),
1699+ "date": _("date")
1700+ }
1701+ finally:
1702+ i18n.set_translation(otrans)
1703+
1704+ for t in i.keys():
1705+ cap = t[0].upper() + t[1:]
1706+ if self.type == cap:
1707+ d["%s_ref" % (t)] = ""
1708+ else:
1709+ d["%s_ref" % (t)] = ('<a href="%s.html#start">[ %s ]</a>'
1710+ % (t, i[t]))
1711+ return quick_maketext(
1712+ 'archidxfoot.html', d,
1713+ mlist=mlist)
1714+
1715+ def html_head(self):
1716+ # avoid i18n side-effects
1717+ mlist = self.maillist
1718+ otrans = i18n.get_translation()
1719+ i18n.set_language(mlist.preferred_language)
1720+ # Convenience
1721+ def quotetime(s):
1722+ return html_quote(i18n.ctime(s), self.lang)
1723+ try:
1724+ d = {"listname": html_quote(mlist.real_name, self.lang),
1725+ "archtype": self.type,
1726+ "archive": self.volNameToDesc(self.archive),
1727+ "listinfo": mlist.GetScriptURL('listinfo', absolute=1),
1728+ "firstdate": quotetime(self.firstdate),
1729+ "lastdate": quotetime(self.lastdate),
1730+ "size": self.size,
1731+ }
1732+ i = {"thread": _("thread"),
1733+ "subject": _("subject"),
1734+ "author": _("author"),
1735+ "date": _("date"),
1736+ }
1737+ finally:
1738+ i18n.set_translation(otrans)
1739+
1740+ for t in i.keys():
1741+ cap = t[0].upper() + t[1:]
1742+ if self.type == cap:
1743+ d["%s_ref" % (t)] = ""
1744+ d["archtype"] = i[t]
1745+ else:
1746+ d["%s_ref" % (t)] = ('<a href="%s.html#start">[ %s ]</a>'
1747+ % (t, i[t]))
1748+ if self.charset:
1749+ d["encoding"] = html_charset % self.charset
1750+ else:
1751+ d["encoding"] = ""
1752+ return quick_maketext(
1753+ 'archidxhead.html', d,
1754+ mlist=mlist)
1755+
1756+ def html_TOC(self):
1757+ mlist = self.maillist
1758+ listname = mlist.internal_name()
1759+ mbox = os.path.join(mlist.archive_dir()+'.mbox', listname+'.mbox')
1760+ d = {"listname": mlist.real_name,
1761+ "listinfo": mlist.GetScriptURL('listinfo', absolute=1),
1762+ "fullarch": '../%s.mbox/%s.mbox' % (listname, listname),
1763+ "size": sizeof(mbox, mlist.preferred_language),
1764+ 'meta': '',
1765+ }
1766+ # Avoid i18n side-effects
1767+ otrans = i18n.get_translation()
1768+ i18n.set_language(mlist.preferred_language)
1769+ try:
1770+ if not self.archives:
1771+ d["noarchive_msg"] = _(
1772+ '<P>Currently, there are no archives. </P>')
1773+ d["archive_listing_start"] = ""
1774+ d["archive_listing_end"] = ""
1775+ d["archive_listing"] = ""
1776+ else:
1777+ d["noarchive_msg"] = ""
1778+ d["archive_listing_start"] = quick_maketext(
1779+ 'archliststart.html',
1780+ lang=mlist.preferred_language,
1781+ mlist=mlist)
1782+ d["archive_listing_end"] = quick_maketext(
1783+ 'archlistend.html',
1784+ mlist=mlist)
1785+
1786+ accum = []
1787+ for a in self.archives:
1788+ accum.append(self.html_TOC_entry(a))
1789+ d["archive_listing"] = EMPTYSTRING.join(accum)
1790+ finally:
1791+ i18n.set_translation(otrans)
1792+ # The TOC is always in the charset of the list's preferred language
1793+ d['meta'] += html_charset % Utils.GetCharSet(mlist.preferred_language)
1794+ # The site can disable public access to the mbox file.
1795+ if mm_cfg.PUBLIC_MBOX:
1796+ template = 'archtoc.html'
1797+ else:
1798+ template = 'archtocnombox.html'
1799+ return quick_maketext(template, d, mlist=mlist)
1800+
1801+ def html_TOC_entry(self, arch):
1802+ # Check to see if the archive is gzip'd or not
1803+ txtfile = os.path.join(self.maillist.archive_dir(), arch + '.txt')
1804+ gzfile = txtfile + '.gz'
1805+ # which exists? .txt.gz first, then .txt
1806+ if os.path.exists(gzfile):
1807+ file = gzfile
1808+ url = arch + '.txt.gz'
1809+ templ = '<td><A href="%(url)s">[ ' + _('Gzip\'d Text%(sz)s') \
1810+ + ']</a></td>'
1811+ elif os.path.exists(txtfile):
1812+ file = txtfile
1813+ url = arch + '.txt'
1814+ templ = '<td><A href="%(url)s">[ ' + _('Text%(sz)s') + ']</a></td>'
1815+ else:
1816+ # neither found?
1817+ file = None
1818+ # in Python 1.5.2 we have an easy way to get the size
1819+ if file:
1820+ textlink = templ % {
1821+ 'url': url,
1822+ 'sz' : sizeof(file, self.maillist.preferred_language)
1823+ }
1824+ else:
1825+ # there's no archive file at all... hmmm.
1826+ textlink = ''
1827+ return quick_maketext(
1828+ 'archtocentry.html',
1829+ {'archive': arch,
1830+ 'archivelabel': self.volNameToDesc(arch),
1831+ 'textlink': textlink
1832+ },
1833+ mlist=self.maillist)
1834+
1835+ def GetArchLock(self):
1836+ if self._lock_file:
1837+ return 1
1838+ self._lock_file = LockFile.LockFile(
1839+ os.path.join(mm_cfg.LOCK_DIR,
1840+ self.maillist.internal_name() + '-arch.lock'))
1841+ try:
1842+ self._lock_file.lock(timeout=0.5)
1843+ except LockFile.TimeOutError:
1844+ return 0
1845+ return 1
1846+
1847+ def DropArchLock(self):
1848+ if self._lock_file:
1849+ self._lock_file.unlock(unconditionally=1)
1850+ self._lock_file = None
1851+
1852+ def processListArch(self):
1853+ name = self.maillist.ArchiveFileName()
1854+ wname= name+'.working'
1855+ ename= name+'.err_unarchived'
1856+ try:
1857+ os.stat(name)
1858+ except (IOError,os.error):
1859+ #no archive file, nothin to do -ddm
1860+ return
1861+
1862+ #see if arch is locked here -ddm
1863+ if not self.GetArchLock():
1864+ #another archiver is running, nothing to do. -ddm
1865+ return
1866+
1867+ #if the working file is still here, the archiver may have
1868+ # crashed during archiving. Save it, log an error, and move on.
1869+ try:
1870+ wf = open(wname)
1871+ syslog('error',
1872+ 'Archive working file %s present. '
1873+ 'Check %s for possibly unarchived msgs',
1874+ wname, ename)
1875+ omask = os.umask(007)
1876+ try:
1877+ ef = open(ename, 'a+')
1878+ finally:
1879+ os.umask(omask)
1880+ ef.seek(1,2)
1881+ if ef.read(1) <> '\n':
1882+ ef.write('\n')
1883+ ef.write(wf.read())
1884+ ef.close()
1885+ wf.close()
1886+ os.unlink(wname)
1887+ except IOError:
1888+ pass
1889+ os.rename(name,wname)
1890+ archfile = open(wname)
1891+ self.processUnixMailbox(archfile)
1892+ archfile.close()
1893+ os.unlink(wname)
1894+ self.DropArchLock()
1895+
1896+ def get_filename(self, article):
1897+ return '%06i.html' % (article.sequence,)
1898+
1899+ def get_archives(self, article):
1900+ """Return a list of indexes where the article should be filed.
1901+ A string can be returned if the list only contains one entry,
1902+ and the empty list is legal."""
1903+ res = self.dateToVolName(float(article.date))
1904+ self.message(C_("figuring article archives\n"))
1905+ self.message(res + "\n")
1906+ return res
1907+
1908+ def volNameToDesc(self, volname):
1909+ volname = volname.strip()
1910+ # Don't make these module global constants since we have to runtime
1911+ # translate them anyway.
1912+ monthdict = [
1913+ '',
1914+ _('January'), _('February'), _('March'), _('April'),
1915+ _('May'), _('June'), _('July'), _('August'),
1916+ _('September'), _('October'), _('November'), _('December')
1917+ ]
1918+ for each in self._volre.keys():
1919+ match = re.match(self._volre[each], volname)
1920+ # Let ValueErrors percolate up
1921+ if match:
1922+ year = int(match.group('year'))
1923+ if each == 'quarter':
1924+ d =["", _("First"), _("Second"), _("Third"), _("Fourth") ]
1925+ ord = d[int(match.group('quarter'))]
1926+ return _("%(ord)s quarter %(year)i")
1927+ elif each == 'month':
1928+ monthstr = match.group('month').lower()
1929+ for i in range(1, 13):
1930+ monthname = time.strftime("%B", (1999,i,1,0,0,0,0,1,0))
1931+ if monthstr.lower() == monthname.lower():
1932+ month = monthdict[i]
1933+ return _("%(month)s %(year)i")
1934+ raise ValueError, "%s is not a month!" % monthstr
1935+ elif each == 'week':
1936+ month = monthdict[int(match.group("month"))]
1937+ day = int(match.group("day"))
1938+ return _("The Week Of Monday %(day)i %(month)s %(year)i")
1939+ elif each == 'day':
1940+ month = monthdict[int(match.group("month"))]
1941+ day = int(match.group("day"))
1942+ return _("%(day)i %(month)s %(year)i")
1943+ else:
1944+ return match.group('year')
1945+ raise ValueError, "%s is not a valid volname" % volname
1946+
1947+# The following two methods should be inverses of each other. -ddm
1948+
1949+ def dateToVolName(self,date):
1950+ datetuple=time.localtime(date)
1951+ if self.ARCHIVE_PERIOD=='year':
1952+ return time.strftime("%Y",datetuple)
1953+ elif self.ARCHIVE_PERIOD=='quarter':
1954+ if datetuple[1] in [1,2,3]:
1955+ return time.strftime("%Yq1",datetuple)
1956+ elif datetuple[1] in [4,5,6]:
1957+ return time.strftime("%Yq2",datetuple)
1958+ elif datetuple[1] in [7,8,9]:
1959+ return time.strftime("%Yq3",datetuple)
1960+ else:
1961+ return time.strftime("%Yq4",datetuple)
1962+ elif self.ARCHIVE_PERIOD == 'day':
1963+ return time.strftime("%Y%m%d", datetuple)
1964+ elif self.ARCHIVE_PERIOD == 'week':
1965+ # Reconstruct "seconds since epoch", and subtract weekday
1966+ # multiplied by the number of seconds in a day.
1967+ monday = time.mktime(datetuple) - datetuple[6] * 24 * 60 * 60
1968+ # Build a new datetuple from this "seconds since epoch" value
1969+ datetuple = time.localtime(monday)
1970+ return time.strftime("Week-of-Mon-%Y%m%d", datetuple)
1971+ # month. -ddm
1972+ else:
1973+ return time.strftime("%Y-%B",datetuple)
1974+
1975+
1976+ def volNameToDate(self, volname):
1977+ volname = volname.strip()
1978+ for each in self._volre.keys():
1979+ match = re.match(self._volre[each],volname)
1980+ if match:
1981+ year = int(match.group('year'))
1982+ month = 1
1983+ day = 1
1984+ if each == 'quarter':
1985+ q = int(match.group('quarter'))
1986+ month = (q * 3) - 2
1987+ elif each == 'month':
1988+ monthstr = match.group('month').lower()
1989+ m = []
1990+ for i in range(1,13):
1991+ m.append(
1992+ time.strftime("%B",(1999,i,1,0,0,0,0,1,0)).lower())
1993+ try:
1994+ month = m.index(monthstr) + 1
1995+ except ValueError:
1996+ pass
1997+ elif each == 'week' or each == 'day':
1998+ month = int(match.group("month"))
1999+ day = int(match.group("day"))
2000+ try:
2001+ return time.mktime((year,month,1,0,0,0,0,1,-1))
2002+ except OverflowError:
2003+ return 0.0
2004+ return 0.0
2005+
2006+ def sortarchives(self):
2007+ def sf(a, b):
2008+ al = self.volNameToDate(a)
2009+ bl = self.volNameToDate(b)
2010+ if al > bl:
2011+ return 1
2012+ elif al < bl:
2013+ return -1
2014+ else:
2015+ return 0
2016+ if self.ARCHIVE_PERIOD in ('month','year','quarter'):
2017+ self.archives.sort(sf)
2018+ else:
2019+ self.archives.sort()
2020+ self.archives.reverse()
2021+
2022+ def message(self, msg):
2023+ if self.VERBOSE:
2024+ f = sys.stderr
2025+ f.write(msg)
2026+ if msg[-1:] != '\n':
2027+ f.write('\n')
2028+ f.flush()
2029+
2030+ def open_new_archive(self, archive, archivedir):
2031+ index_html = os.path.join(archivedir, 'index.html')
2032+ try:
2033+ os.unlink(index_html)
2034+ except:
2035+ pass
2036+ os.symlink(self.DEFAULTINDEX+'.html',index_html)
2037+
2038+ def write_index_header(self):
2039+ self.depth=0
2040+ print self.html_head()
2041+ if not self.THREADLAZY and self.type=='Thread':
2042+ self.message(C_("Computing threaded index\n"))
2043+ self.updateThreadedIndex()
2044+
2045+ def write_index_footer(self):
2046+ for i in range(self.depth):
2047+ print '</UL>'
2048+ print self.html_foot()
2049+
2050+ def write_index_entry(self, article):
2051+ subject = self.get_header("subject", article)
2052+ author = self.get_header("author", article)
2053+ if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS:
2054+ try:
2055+ author = re.sub('@', _(' at '), author)
2056+ except UnicodeError:
2057+ # Non-ASCII author contains '@' ... no valid email anyway
2058+ pass
2059+ subject = CGIescape(subject, self.lang)
2060+ author = CGIescape(author, self.lang)
2061+
2062+ d = {
2063+ 'filename': urllib.quote(article.filename),
2064+ 'subject': subject,
2065+ 'sequence': article.sequence,
2066+ 'author': author
2067+ }
2068+ print quick_maketext(
2069+ 'archidxentry.html', d,
2070+ mlist=self.maillist)
2071+
2072+ def get_header(self, field, article):
2073+ # if we have no decoded header, return the encoded one
2074+ result = article.decoded.get(field)
2075+ if result is None:
2076+ return getattr(article, field)
2077+ # otherwise, the decoded one will be Unicode
2078+ return result
2079+
2080+ def write_threadindex_entry(self, article, depth):
2081+ if depth < 0:
2082+ self.message('depth<0')
2083+ depth = 0
2084+ if depth > self.THREADLEVELS:
2085+ depth = self.THREADLEVELS
2086+ if depth < self.depth:
2087+ for i in range(self.depth-depth):
2088+ print '</UL>'
2089+ elif depth > self.depth:
2090+ for i in range(depth-self.depth):
2091+ print '<UL>'
2092+ print '<!--%i %s -->' % (depth, article.threadKey)
2093+ self.depth = depth
2094+ self.write_index_entry(article)
2095+
2096+ def write_TOC(self):
2097+ self.sortarchives()
2098+ omask = os.umask(002)
2099+ try:
2100+ toc = open(os.path.join(self.basedir, 'index.html'), 'w')
2101+ finally:
2102+ os.umask(omask)
2103+ toc.write(self.html_TOC())
2104+ toc.close()
2105+
2106+ def write_article(self, index, article, path):
2107+ # called by add_article
2108+ omask = os.umask(002)
2109+ try:
2110+ f = open(path, 'w')
2111+ finally:
2112+ os.umask(omask)
2113+ f.write(article.as_html())
2114+ f.close()
2115+
2116+ # Write the text article to the text archive.
2117+ path = os.path.join(self.basedir, "%s.txt" % index)
2118+ omask = os.umask(002)
2119+ try:
2120+ f = open(path, 'a+')
2121+ finally:
2122+ os.umask(omask)
2123+ f.write(article.as_text())
2124+ f.close()
2125+
2126+ def update_archive(self, archive):
2127+ self.__super_update_archive(archive)
2128+ # only do this if the gzip module was imported globally, and
2129+ # gzip'ing was enabled via mm_cfg.GZIP_ARCHIVE_TXT_FILES. See
2130+ # above.
2131+ if gzip:
2132+ archz = None
2133+ archt = None
2134+ txtfile = os.path.join(self.basedir, '%s.txt' % archive)
2135+ gzipfile = os.path.join(self.basedir, '%s.txt.gz' % archive)
2136+ oldgzip = os.path.join(self.basedir, '%s.old.txt.gz' % archive)
2137+ try:
2138+ # open the plain text file
2139+ archt = open(txtfile)
2140+ except IOError:
2141+ return
2142+ try:
2143+ os.rename(gzipfile, oldgzip)
2144+ archz = gzip.open(oldgzip)
2145+ except (IOError, RuntimeError, os.error):
2146+ pass
2147+ try:
2148+ ou = os.umask(002)
2149+ newz = gzip.open(gzipfile, 'w')
2150+ finally:
2151+ # XXX why is this a finally?
2152+ os.umask(ou)
2153+ if archz:
2154+ newz.write(archz.read())
2155+ archz.close()
2156+ os.unlink(oldgzip)
2157+ # XXX do we really need all this in a try/except?
2158+ try:
2159+ newz.write(archt.read())
2160+ newz.close()
2161+ archt.close()
2162+ except IOError:
2163+ pass
2164+ os.unlink(txtfile)
2165+
2166+ _skip_attrs = ('maillist', '_lock_file', 'charset')
2167+
2168+ def getstate(self):
2169+ d={}
2170+ for each in self.__dict__.keys():
2171+ if not (each in self._skip_attrs
2172+ or each.upper() == each):
2173+ d[each] = self.__dict__[each]
2174+ return d
2175+
2176+ # Add <A HREF="..."> tags around URLs and e-mail addresses.
2177+
2178+ def __processbody_URLquote(self, lines):
2179+ # XXX a lot to do here:
2180+ # 1. use lines directly, rather than source and dest
2181+ # 2. make it clearer
2182+ # 3. make it faster
2183+ # TK: Prepare for unicode obscure.
2184+ atmark = _(' at ')
2185+ if lines and isinstance(lines[0], types.UnicodeType):
2186+ atmark = unicode(atmark, Utils.GetCharSet(self.lang), 'replace')
2187+ source = lines[:]
2188+ dest = lines
2189+ last_line_was_quoted = 0
2190+ for i in xrange(0, len(source)):
2191+ Lorig = L = source[i]
2192+ prefix = suffix = ""
2193+ if L is None:
2194+ continue
2195+ # Italicise quoted text
2196+ if self.IQUOTES:
2197+ quoted = quotedpat.match(L)
2198+ if quoted is None:
2199+ last_line_was_quoted = 0
2200+ else:
2201+ quoted = quoted.end(0)
2202+ prefix = CGIescape(L[:quoted], self.lang) + '<i>'
2203+ suffix = '</I>'
2204+ if self.SHOWHTML:
2205+ suffix += '<BR>'
2206+ if not last_line_was_quoted:
2207+ prefix = '<BR>' + prefix
2208+ L = L[quoted:]
2209+ last_line_was_quoted = 1
2210+ # Check for an e-mail address
2211+ L2 = ""
2212+ jr = emailpat.search(L)
2213+ kr = urlpat.search(L)
2214+ while jr is not None or kr is not None:
2215+ if jr == None:
2216+ j = -1
2217+ else:
2218+ j = jr.start(0)
2219+ if kr is None:
2220+ k = -1
2221+ else:
2222+ k = kr.start(0)
2223+ if j != -1 and (j < k or k == -1):
2224+ text = jr.group(1)
2225+ length = len(text)
2226+ if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS:
2227+ text = re.sub('@', atmark, text)
2228+ URL = self.maillist.GetScriptURL(
2229+ 'listinfo', absolute=1)
2230+ else:
2231+ URL = 'mailto:' + text
2232+ pos = j
2233+ elif k != -1 and (j > k or j == -1):
2234+ text = URL = kr.group(1)
2235+ length = len(text)
2236+ pos = k
2237+ else: # j==k
2238+ raise ValueError, "j==k: This can't happen!"
2239+ #length = len(text)
2240+ #self.message("URL: %s %s %s \n"
2241+ # % (CGIescape(L[:pos]), URL, CGIescape(text)))
2242+ L2 += '%s<A HREF="%s">%s</A>' % (
2243+ CGIescape(L[:pos], self.lang),
2244+ html_quote(URL), CGIescape(text, self.lang))
2245+ L = L[pos+length:]
2246+ jr = emailpat.search(L)
2247+ kr = urlpat.search(L)
2248+ if jr is None and kr is None:
2249+ L = CGIescape(L, self.lang)
2250+ L = prefix + L2 + L + suffix
2251+ source[i] = None
2252+ dest[i] = L
2253+
2254+ # Perform Hypermail-style processing of <HTML></HTML> directives
2255+ # in message bodies. Lines between <HTML> and </HTML> will be written
2256+ # out precisely as they are; other lines will be passed to func2
2257+ # for further processing .
2258+
2259+ def __processbody_HTML(self, lines):
2260+ # XXX need to make this method modify in place
2261+ source = lines[:]
2262+ dest = lines
2263+ l = len(source)
2264+ i = 0
2265+ while i < l:
2266+ while i < l and htmlpat.match(source[i]) is None:
2267+ i = i + 1
2268+ if i < l:
2269+ source[i] = None
2270+ i = i + 1
2271+ while i < l and nohtmlpat.match(source[i]) is None:
2272+ dest[i], source[i] = source[i], None
2273+ i = i + 1
2274+ if i < l:
2275+ source[i] = None
2276+ i = i + 1
2277+
2278+ def format_article(self, article):
2279+ # called from add_article
2280+ # TBD: Why do the HTML formatting here and keep it in the
2281+ # pipermail database? It makes more sense to do the html
2282+ # formatting as the article is being written as html and toss
2283+ # the data after it has been written to the archive file.
2284+ lines = filter(None, article.body)
2285+ # Handle <HTML> </HTML> directives
2286+ if self.ALLOWHTML:
2287+ self.__processbody_HTML(lines)
2288+ self.__processbody_URLquote(lines)
2289+ if not self.SHOWHTML and lines:
2290+ lines.insert(0, '<PRE>')
2291+ lines.append('</PRE>')
2292+ else:
2293+ # Do fancy formatting here
2294+ if self.SHOWBR:
2295+ lines = map(lambda x:x + "<BR>", lines)
2296+ else:
2297+ for i in range(0, len(lines)):
2298+ s = lines[i]
2299+ if s[0:1] in ' \t\n':
2300+ lines[i] = '<P>' + s
2301+ article.html_body = lines
2302+ return article
2303+
2304+ def update_article(self, arcdir, article, prev, next):
2305+ seq = article.sequence
2306+ filename = os.path.join(arcdir, article.filename)
2307+ self.message(C_('Updating HTML for article %(seq)s'))
2308+ try:
2309+ f = open(filename)
2310+ article.loadbody_fromHTML(f)
2311+ f.close()
2312+ except IOError, e:
2313+ if e.errno <> errno.ENOENT: raise
2314+ self.message(C_('article file %(filename)s is missing!'))
2315+ article.prev = prev
2316+ article.next = next
2317+ omask = os.umask(002)
2318+ try:
2319+ f = open(filename, 'w')
2320+ finally:
2321+ os.umask(omask)
2322+ f.write(article.as_html())
2323+ f.close()
2324
2325=== added file 'Mailman/Archiver/HyperDatabase.py'
2326--- Mailman/Archiver/HyperDatabase.py 1970-01-01 00:00:00 +0000
2327+++ Mailman/Archiver/HyperDatabase.py 2025-02-01 12:21:11 +0000
2328@@ -0,0 +1,344 @@
2329+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
2330+#
2331+# This program is free software; you can redistribute it and/or
2332+# modify it under the terms of the GNU General Public License
2333+# as published by the Free Software Foundation; either version 2
2334+# of the License, or (at your option) any later version.
2335+#
2336+# This program is distributed in the hope that it will be useful,
2337+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2338+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2339+# GNU General Public License for more details.
2340+#
2341+# You should have received a copy of the GNU General Public License
2342+# along with this program; if not, write to the Free Software
2343+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
2344+# USA.
2345+
2346+#
2347+# site modules
2348+#
2349+import os
2350+import marshal
2351+import time
2352+import errno
2353+
2354+#
2355+# package/project modules
2356+#
2357+import pipermail
2358+from Mailman import LockFile
2359+
2360+CACHESIZE = pipermail.CACHESIZE
2361+
2362+try:
2363+ import cPickle
2364+ pickle = cPickle
2365+except ImportError:
2366+ import pickle
2367+
2368+#
2369+# we're using a python dict in place of
2370+# of bsddb.btree database. only defining
2371+# the parts of the interface used by class HyperDatabase
2372+# only one thing can access this at a time.
2373+#
2374+class DumbBTree:
2375+ """Stores pickles of Article objects
2376+
2377+ This dictionary-like object stores pickles of all the Article
2378+ objects. The object itself is stored using marshal. It would be
2379+ much simpler, and probably faster, to store the actual objects in
2380+ the DumbBTree and pickle it.
2381+
2382+ TBD: Also needs a more sensible name, like IteratableDictionary or
2383+ SortedDictionary.
2384+ """
2385+
2386+ def __init__(self, path):
2387+ self.current_index = 0
2388+ self.path = path
2389+ self.lockfile = LockFile.LockFile(self.path + ".lock")
2390+ self.lock()
2391+ self.__dirty = 0
2392+ self.dict = {}
2393+ self.sorted = []
2394+ self.load()
2395+
2396+ def __repr__(self):
2397+ return "DumbBTree(%s)" % self.path
2398+
2399+ def __sort(self, dirty=None):
2400+ if self.__dirty == 1 or dirty:
2401+ self.sorted = self.dict.keys()
2402+ self.sorted.sort()
2403+ self.__dirty = 0
2404+
2405+ def lock(self):
2406+ self.lockfile.lock()
2407+
2408+ def unlock(self):
2409+ try:
2410+ self.lockfile.unlock()
2411+ except LockFile.NotLockedError:
2412+ pass
2413+
2414+ def __delitem__(self, item):
2415+ # if first hasn't been called, we can skip the sort
2416+ if self.current_index == 0:
2417+ del self.dict[item]
2418+ self.__dirty = 1
2419+ return
2420+ try:
2421+ ci = self.sorted[self.current_index]
2422+ except IndexError:
2423+ ci = None
2424+ if ci == item:
2425+ try:
2426+ ci = self.sorted[self.current_index + 1]
2427+ except IndexError:
2428+ ci = None
2429+ del self.dict[item]
2430+ self.__sort(dirty=1)
2431+ if ci is not None:
2432+ self.current_index = self.sorted.index(ci)
2433+ else:
2434+ self.current_index = self.current_index + 1
2435+
2436+ def clear(self):
2437+ # bulk clearing much faster than deleting each item, esp. with the
2438+ # implementation of __delitem__() above :(
2439+ self.dict = {}
2440+
2441+ def first(self):
2442+ self.__sort() # guarantee that the list is sorted
2443+ if not self.sorted:
2444+ raise KeyError
2445+ else:
2446+ key = self.sorted[0]
2447+ self.current_index = 1
2448+ return key, self.dict[key]
2449+
2450+ def last(self):
2451+ if not self.sorted:
2452+ raise KeyError
2453+ else:
2454+ key = self.sorted[-1]
2455+ self.current_index = len(self.sorted) - 1
2456+ return key, self.dict[key]
2457+
2458+ def next(self):
2459+ try:
2460+ key = self.sorted[self.current_index]
2461+ except IndexError:
2462+ raise KeyError
2463+ self.current_index = self.current_index + 1
2464+ return key, self.dict[key]
2465+
2466+ def has_key(self, key):
2467+ return self.dict.has_key(key)
2468+
2469+ def set_location(self, loc):
2470+ index = 0
2471+ self.__sort()
2472+ for key in self.sorted:
2473+ if key[0] == loc:
2474+ self.current_index = index
2475+ return key,self.dict[key]
2476+ index = index + 1
2477+ raise KeyError(loc)
2478+
2479+ def __getitem__(self, item):
2480+ return self.dict[item]
2481+
2482+ def __setitem__(self, item, val):
2483+ # if first hasn't been called, then we don't need to worry
2484+ # about sorting again
2485+ if self.current_index == 0:
2486+ self.dict[item] = val
2487+ self.__dirty = 1
2488+ return
2489+ try:
2490+ current_item = self.sorted[self.current_index]
2491+ except IndexError:
2492+ current_item = item
2493+ self.dict[item] = val
2494+ self.__sort(dirty=1)
2495+ self.current_index = self.sorted.index(current_item)
2496+
2497+ def __len__(self):
2498+ return len(self.sorted)
2499+
2500+ def load(self):
2501+ try:
2502+ fp = open(self.path)
2503+ try:
2504+ self.dict = marshal.load(fp)
2505+ finally:
2506+ fp.close()
2507+ except IOError, e:
2508+ if e.errno <> errno.ENOENT: raise
2509+ pass
2510+ except EOFError:
2511+ pass
2512+ else:
2513+ self.__sort(dirty=1)
2514+
2515+ def close(self):
2516+ omask = os.umask(007)
2517+ try:
2518+ fp = open(self.path, 'w')
2519+ finally:
2520+ os.umask(omask)
2521+ fp.write(marshal.dumps(self.dict))
2522+ fp.close()
2523+ self.unlock()
2524+
2525+
2526
2527+# this is lifted straight out of pipermail with
2528+# the bsddb.btree replaced with above class.
2529+# didn't use inheritance because of all the
2530+# __internal stuff that needs to be here -scott
2531+#
2532+class HyperDatabase(pipermail.Database):
2533+ __super_addArticle = pipermail.Database.addArticle
2534+
2535+ def __init__(self, basedir, mlist):
2536+ self.__cache = {}
2537+ self.__currentOpenArchive = None # The currently open indices
2538+ self._mlist = mlist
2539+ self.basedir = os.path.expanduser(basedir)
2540+ # Recently added articles, indexed only by message ID
2541+ self.changed={}
2542+
2543+ def firstdate(self, archive):
2544+ self.__openIndices(archive)
2545+ date = 'None'
2546+ try:
2547+ datekey, msgid = self.dateIndex.first()
2548+ date = time.asctime(time.localtime(float(datekey[0])))
2549+ except KeyError:
2550+ pass
2551+ return date
2552+
2553+ def lastdate(self, archive):
2554+ self.__openIndices(archive)
2555+ date = 'None'
2556+ try:
2557+ datekey, msgid = self.dateIndex.last()
2558+ date = time.asctime(time.localtime(float(datekey[0])))
2559+ except KeyError:
2560+ pass
2561+ return date
2562+
2563+ def numArticles(self, archive):
2564+ self.__openIndices(archive)
2565+ return len(self.dateIndex)
2566+
2567+ def addArticle(self, archive, article, subject=None, author=None,
2568+ date=None):
2569+ self.__openIndices(archive)
2570+ self.__super_addArticle(archive, article, subject, author, date)
2571+
2572+ def __openIndices(self, archive):
2573+ if self.__currentOpenArchive == archive:
2574+ return
2575+ self.__closeIndices()
2576+ arcdir = os.path.join(self.basedir, 'database')
2577+ omask = os.umask(0)
2578+ try:
2579+ try:
2580+ os.mkdir(arcdir, 02770)
2581+ except OSError, e:
2582+ if e.errno <> errno.EEXIST: raise
2583+ finally:
2584+ os.umask(omask)
2585+ for i in ('date', 'author', 'subject', 'article', 'thread'):
2586+ t = DumbBTree(os.path.join(arcdir, archive + '-' + i))
2587+ setattr(self, i + 'Index', t)
2588+ self.__currentOpenArchive = archive
2589+
2590+ def __closeIndices(self):
2591+ for i in ('date', 'author', 'subject', 'thread', 'article'):
2592+ attr = i + 'Index'
2593+ if hasattr(self, attr):
2594+ index = getattr(self, attr)
2595+ if i == 'article':
2596+ if not hasattr(self, 'archive_length'):
2597+ self.archive_length = {}
2598+ l = len(index)
2599+ self.archive_length[self.__currentOpenArchive] = l
2600+ index.close()
2601+ delattr(self, attr)
2602+ self.__currentOpenArchive = None
2603+
2604+ def close(self):
2605+ self.__closeIndices()
2606+
2607+ def hasArticle(self, archive, msgid):
2608+ self.__openIndices(archive)
2609+ return self.articleIndex.has_key(msgid)
2610+
2611+ def setThreadKey(self, archive, key, msgid):
2612+ self.__openIndices(archive)
2613+ self.threadIndex[key]=msgid
2614+
2615+ def getArticle(self, archive, msgid):
2616+ self.__openIndices(archive)
2617+ if not self.__cache.has_key(msgid):
2618+ # get the pickled object out of the DumbBTree
2619+ buf = self.articleIndex[msgid]
2620+ article = self.__cache[msgid] = pickle.loads(buf)
2621+ # For upgrading older archives
2622+ article.setListIfUnset(self._mlist)
2623+ else:
2624+ article = self.__cache[msgid]
2625+ return article
2626+
2627+ def first(self, archive, index):
2628+ self.__openIndices(archive)
2629+ index = getattr(self, index + 'Index')
2630+ try:
2631+ key, msgid = index.first()
2632+ return msgid
2633+ except KeyError:
2634+ return None
2635+
2636+ def next(self, archive, index):
2637+ self.__openIndices(archive)
2638+ index = getattr(self, index + 'Index')
2639+ try:
2640+ key, msgid = index.next()
2641+ return msgid
2642+ except KeyError:
2643+ return None
2644+
2645+ def getOldestArticle(self, archive, subject):
2646+ self.__openIndices(archive)
2647+ subject = subject.lower()
2648+ try:
2649+ self.subjectIndex.set_location(subject)
2650+ key, tempid = self.subjectIndex.next()
2651+ [subject2, date]= key[:2]
2652+ if subject!=subject2: return None
2653+ return tempid
2654+ except KeyError:
2655+ return None
2656+
2657+ def newArchive(self, archive):
2658+ pass
2659+
2660+ def clearIndex(self, archive, index):
2661+ self.__openIndices(archive)
2662+ if hasattr(self.threadIndex, 'clear'):
2663+ self.threadIndex.clear()
2664+ return
2665+ finished=0
2666+ try:
2667+ key, msgid=self.threadIndex.first()
2668+ except KeyError: finished=1
2669+ while not finished:
2670+ del self.threadIndex[key]
2671+ try:
2672+ key, msgid=self.threadIndex.next()
2673+ except KeyError: finished=1
2674
2675=== added file 'Mailman/Archiver/Makefile.in'
2676--- Mailman/Archiver/Makefile.in 1970-01-01 00:00:00 +0000
2677+++ Mailman/Archiver/Makefile.in 2025-02-01 12:21:11 +0000
2678@@ -0,0 +1,73 @@
2679+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
2680+#
2681+# This program is free software; you can redistribute it and/or
2682+# modify it under the terms of the GNU General Public License
2683+# as published by the Free Software Foundation; either version 2
2684+# of the License, or (at your option) any later version.
2685+#
2686+# This program is distributed in the hope that it will be useful,
2687+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2688+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2689+# GNU General Public License for more details.
2690+#
2691+# You should have received a copy of the GNU General Public License
2692+# along with this program; if not, write to the Free Software
2693+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2694+
2695+# NOTE: Makefile.in is converted into Makefile by the configure script
2696+# in the parent directory. Once configure has run, you can recreate
2697+# the Makefile by running just config.status.
2698+
2699+# Variables set by configure
2700+
2701+VPATH= @srcdir@
2702+srcdir= @srcdir@
2703+bindir= @bindir@
2704+prefix= @prefix@
2705+exec_prefix= @exec_prefix@
2706+DESTDIR=
2707+
2708+CC= @CC@
2709+CHMOD= @CHMOD@
2710+INSTALL= @INSTALL@
2711+
2712+DEFS= @DEFS@
2713+
2714+# Customizable but not set by configure
2715+
2716+OPT= @OPT@
2717+CFLAGS= $(OPT) $(DEFS)
2718+PACKAGEDIR= $(prefix)/Mailman/Archiver
2719+SHELL= /bin/sh
2720+
2721+MODULES= __init__.py Archiver.py HyperArch.py HyperDatabase.py \
2722+pipermail.py
2723+
2724+
2725+# Modes for directories and executables created by the install
2726+# process. Default to group-writable directories but
2727+# user-only-writable for executables.
2728+DIRMODE= 775
2729+EXEMODE= 755
2730+FILEMODE= 644
2731+INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
2732+
2733+
2734+# Rules
2735+
2736+all:
2737+
2738+install:
2739+ for f in $(MODULES); \
2740+ do \
2741+ $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
2742+ done
2743+
2744+finish:
2745+
2746+clean:
2747+
2748+distclean:
2749+ -rm *.pyc
2750+ -rm Makefile
2751+
2752
2753=== added file 'Mailman/Archiver/__init__.py'
2754--- Mailman/Archiver/__init__.py 1970-01-01 00:00:00 +0000
2755+++ Mailman/Archiver/__init__.py 2025-02-01 12:21:11 +0000
2756@@ -0,0 +1,17 @@
2757+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
2758+#
2759+# This program is free software; you can redistribute it and/or
2760+# modify it under the terms of the GNU General Public License
2761+# as published by the Free Software Foundation; either version 2
2762+# of the License, or (at your option) any later version.
2763+#
2764+# This program is distributed in the hope that it will be useful,
2765+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2766+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2767+# GNU General Public License for more details.
2768+#
2769+# You should have received a copy of the GNU General Public License
2770+# along with this program; if not, write to the Free Software
2771+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2772+
2773+from Archiver import *
2774
2775=== added file 'Mailman/Archiver/pipermail.py'
2776--- Mailman/Archiver/pipermail.py 1970-01-01 00:00:00 +0000
2777+++ Mailman/Archiver/pipermail.py 2025-02-01 12:21:11 +0000
2778@@ -0,0 +1,908 @@
2779+#! /usr/bin/env python
2780+
2781+from __future__ import nested_scopes
2782+
2783+import mailbox
2784+import os
2785+import re
2786+import sys
2787+import time
2788+from email.Utils import parseaddr, parsedate_tz, mktime_tz, formatdate
2789+import cPickle as pickle
2790+from cStringIO import StringIO
2791+from string import lowercase
2792+
2793+# Work around for some misguided Python packages that add iso-8859-1
2794+# accented characters to string.lowercase.
2795+lowercase = lowercase[:26]
2796+
2797+__version__ = '0.09 (Mailman edition)'
2798+VERSION = __version__
2799+CACHESIZE = 100 # Number of slots in the cache
2800+
2801+from Mailman import mm_cfg
2802+from Mailman import Errors
2803+from Mailman.Mailbox import ArchiverMailbox
2804+from Mailman.Logging.Syslog import syslog
2805+from Mailman.i18n import _, C_
2806+
2807+# True/False
2808+try:
2809+ True, False
2810+except NameError:
2811+ True = 1
2812+ False = 0
2813+
2814+SPACE = ' '
2815+
2816+
2817+
2818
2819+msgid_pat = re.compile(r'(<.*>)')
2820+def strip_separators(s):
2821+ "Remove quotes or parenthesization from a Message-ID string"
2822+ if not s:
2823+ return ""
2824+ if s[0] in '"<([' and s[-1] in '">)]':
2825+ s = s[1:-1]
2826+ return s
2827+
2828+smallNameParts = ['van', 'von', 'der', 'de']
2829+
2830+def fixAuthor(author):
2831+ "Canonicalize a name into Last, First format"
2832+ # If there's a comma, guess that it's already in "Last, First" format
2833+ if ',' in author:
2834+ return author
2835+ L = author.split()
2836+ i = len(L) - 1
2837+ if i == 0:
2838+ return author # The string's one word--forget it
2839+ if author.upper() == author or author.lower() == author:
2840+ # Damn, the name is all upper- or lower-case.
2841+ while i > 0 and L[i-1].lower() in smallNameParts:
2842+ i = i - 1
2843+ else:
2844+ # Mixed case; assume that small parts of the last name will be
2845+ # in lowercase, and check them against the list.
2846+ while i>0 and (L[i-1][0] in lowercase or
2847+ L[i-1].lower() in smallNameParts):
2848+ i = i - 1
2849+ author = SPACE.join(L[-1:] + L[i:-1]) + ', ' + SPACE.join(L[:i])
2850+ return author
2851+
2852+# Abstract class for databases
2853+
2854+class DatabaseInterface:
2855+ def __init__(self): pass
2856+ def close(self): pass
2857+ def getArticle(self, archive, msgid): pass
2858+ def hasArticle(self, archive, msgid): pass
2859+ def addArticle(self, archive, article, subject=None, author=None,
2860+ date=None): pass
2861+ def firstdate(self, archive): pass
2862+ def lastdate(self, archive): pass
2863+ def first(self, archive, index): pass
2864+ def next(self, archive, index): pass
2865+ def numArticles(self, archive): pass
2866+ def newArchive(self, archive): pass
2867+ def setThreadKey(self, archive, key, msgid): pass
2868+ def getOldestArticle(self, subject): pass
2869+
2870+class Database(DatabaseInterface):
2871+ """Define the basic sorting logic for a database
2872+
2873+ Assumes that the database internally uses dateIndex, authorIndex,
2874+ etc.
2875+ """
2876+
2877+ # TBD Factor out more of the logic shared between BSDDBDatabase
2878+ # and HyperDatabase and place it in this class.
2879+
2880+ def __init__(self):
2881+ # This method need not be called by subclasses that do their
2882+ # own initialization.
2883+ self.dateIndex = {}
2884+ self.authorIndex = {}
2885+ self.subjectIndex = {}
2886+ self.articleIndex = {}
2887+ self.changed = {}
2888+
2889+ def addArticle(self, archive, article, subject=None, author=None,
2890+ date=None):
2891+ # create the keys; always end w/ msgid which will be unique
2892+ authorkey = (author or article.author, article.date,
2893+ article.msgid)
2894+ subjectkey = (subject or article.subject, article.date,
2895+ article.msgid)
2896+ datekey = date or article.date, article.msgid
2897+
2898+ # Add the new article
2899+ self.dateIndex[datekey] = article.msgid
2900+ self.authorIndex[authorkey] = article.msgid
2901+ self.subjectIndex[subjectkey] = article.msgid
2902+
2903+ self.store_article(article)
2904+ self.changed[archive, article.msgid] = None
2905+
2906+ parentID = article.parentID
2907+ if parentID is not None and self.articleIndex.has_key(parentID):
2908+ parent = self.getArticle(archive, parentID)
2909+ myThreadKey = (parent.threadKey + article.date + '.'
2910+ + str(article.sequence) + '-')
2911+ else:
2912+ myThreadKey = article.date + '.' + str(article.sequence) + '-'
2913+ article.threadKey = myThreadKey
2914+ key = myThreadKey, article.msgid
2915+ self.setThreadKey(archive, key, article.msgid)
2916+
2917+ def store_article(self, article):
2918+ """Store article without message body to save space"""
2919+ # TBD this is not thread safe!
2920+ temp = article.body
2921+ temp2 = article.html_body
2922+ article.body = []
2923+ del article.html_body
2924+ self.articleIndex[article.msgid] = pickle.dumps(article)
2925+ article.body = temp
2926+ article.html_body = temp2
2927+
2928+
2929+# The Article class encapsulates a single posting. The attributes
2930+# are:
2931+#
2932+# sequence : Sequence number, unique for each article in a set of archives
2933+# subject : Subject
2934+# datestr : The posting date, in human-readable format
2935+# date : The posting date, in purely numeric format
2936+# headers : Any other headers of interest
2937+# author : The author's name (and possibly organization)
2938+# email : The author's e-mail address
2939+# msgid : A unique message ID
2940+# in_reply_to: If != "", this is the msgid of the article being replied to
2941+# references : A (possibly empty) list of msgid's of earlier articles
2942+# in the thread
2943+# body : A list of strings making up the message body
2944+
2945+class Article:
2946+ _last_article_time = time.time()
2947+
2948+ def __init__(self, message = None, sequence = 0, keepHeaders = []):
2949+ if message is None:
2950+ return
2951+ self.sequence = sequence
2952+
2953+ self.parentID = None
2954+ self.threadKey = None
2955+ # otherwise the current sequence number is used.
2956+ id = strip_separators(message['Message-Id'])
2957+ if id == "":
2958+ self.msgid = str(self.sequence)
2959+ else: self.msgid = id
2960+
2961+ if message.has_key('Subject'):
2962+ self.subject = str(message['Subject'])
2963+ else:
2964+ self.subject = _('No subject')
2965+ if self.subject == "": self.subject = _('No subject')
2966+
2967+ self._set_date(message)
2968+
2969+ # Figure out the e-mail address and poster's name. Use the From:
2970+ # field first, followed by Reply-To:
2971+ self.author, self.email = parseaddr(message.get('From', ''))
2972+ e = message['Reply-To']
2973+ if not self.email and e is not None:
2974+ ignoreauthor, self.email = parseaddr(e)
2975+ self.email = strip_separators(self.email)
2976+ self.author = strip_separators(self.author)
2977+
2978+ if self.author == "":
2979+ self.author = self.email
2980+
2981+ # Save the In-Reply-To:, References:, and Message-ID: lines
2982+ #
2983+ # TBD: The original code does some munging on these fields, which
2984+ # shouldn't be necessary, but changing this may break code. For
2985+ # safety, I save the original headers on different attributes for use
2986+ # in writing the plain text periodic flat files.
2987+ self._in_reply_to = message['in-reply-to']
2988+ self._references = message['references']
2989+ self._message_id = message['message-id']
2990+
2991+ i_r_t = message['In-Reply-To']
2992+ if i_r_t is None:
2993+ self.in_reply_to = ''
2994+ else:
2995+ match = msgid_pat.search(i_r_t)
2996+ if match is None: self.in_reply_to = ''
2997+ else: self.in_reply_to = strip_separators(match.group(1))
2998+
2999+ references = message['References']
3000+ if references is None:
3001+ self.references = []
3002+ else:
3003+ self.references = map(strip_separators, references.split())
3004+
3005+ # Save any other interesting headers
3006+ self.headers = {}
3007+ for i in keepHeaders:
3008+ if message.has_key(i):
3009+ self.headers[i] = message[i]
3010+
3011+ # Read the message body
3012+ s = StringIO(message.get_payload(decode=True)\
3013+ or message.as_string().split('\n\n',1)[1])
3014+ self.body = s.readlines()
3015+
3016+ def _set_date(self, message):
3017+ def floatdate(datestr):
3018+ if not datestr:
3019+ return None
3020+ date = parsedate_tz(datestr)
3021+ try:
3022+ date = mktime_tz(date)
3023+ if (date < 0 or
3024+ date - time.time() >
3025+ mm_cfg.ARCHIVER_ALLOWABLE_SANE_DATE_SKEW
3026+ ):
3027+ return None
3028+ return date
3029+ except (TypeError, ValueError, OverflowError):
3030+ return None
3031+ date = floatdate(message.get('date'))
3032+ if date is None:
3033+ date = floatdate(message.get('x-list-received-date'))
3034+ if date is None:
3035+ rec_re = re.compile(r'^.*;\s*', re.DOTALL)
3036+ date = floatdate(rec_re.sub('', message.get('received', '')))
3037+ if date is None:
3038+ date = floatdate(re.sub(r'From \s*\S+\s+', '',
3039+ message.get_unixfrom() or '' ))
3040+ if date is None:
3041+ date = self._last_article_time + 1
3042+ self._last_article_time = date
3043+ self.date = '%011i' % date
3044+ self.datestr = message.get('date') \
3045+ or message.get('x-list-received-date') \
3046+ or formatdate(date)
3047+
3048+ def __repr__(self):
3049+ return '<Article ID = '+repr(self.msgid)+'>'
3050+
3051+ def finished_update_article(self):
3052+ pass
3053+
3054+# Pipermail formatter class
3055+
3056+class T:
3057+ DIRMODE = 0755 # Mode to give to created directories
3058+ FILEMODE = 0644 # Mode to give to created files
3059+ INDEX_EXT = ".html" # Extension for indexes
3060+
3061+ def __init__(self, basedir = None, reload = 1, database = None):
3062+ # If basedir isn't provided, assume the current directory
3063+ if basedir is None:
3064+ self.basedir = os.getcwd()
3065+ else:
3066+ basedir = os.path.expanduser(basedir)
3067+ self.basedir = basedir
3068+ self.database = database
3069+
3070+ # If the directory doesn't exist, create it. This code shouldn't get
3071+ # run anymore, we create the directory in Archiver.py. It should only
3072+ # get used by legacy lists created that are only receiving their first
3073+ # message in the HTML archive now -- Marc
3074+ try:
3075+ os.stat(self.basedir)
3076+ except os.error, errdata:
3077+ errno, errmsg = errdata
3078+ if errno != 2:
3079+ raise os.error, errdata
3080+ else:
3081+ self.message(C_('Creating archive directory ') + self.basedir)
3082+ omask = os.umask(0)
3083+ try:
3084+ os.mkdir(self.basedir, self.DIRMODE)
3085+ finally:
3086+ os.umask(omask)
3087+
3088+ # Try to load previously pickled state
3089+ try:
3090+ if not reload:
3091+ raise IOError
3092+ f = open(os.path.join(self.basedir, 'pipermail.pck'), 'r')
3093+ self.message(C_('Reloading pickled archive state'))
3094+ d = pickle.load(f)
3095+ f.close()
3096+ for key, value in d.items():
3097+ setattr(self, key, value)
3098+ except (IOError, EOFError):
3099+ # No pickled version, so initialize various attributes
3100+ self.archives = [] # Archives
3101+ self._dirty_archives = [] # Archives that will have to be updated
3102+ self.sequence = 0 # Sequence variable used for
3103+ # numbering articles
3104+ self.update_TOC = 0 # Does the TOC need updating?
3105+ #
3106+ # make the basedir variable work when passed in as an __init__ arg
3107+ # and different from the one in the pickle. Let the one passed in
3108+ # as an __init__ arg take precedence if it's stated. This way, an
3109+ # archive can be moved from one place to another and still work.
3110+ #
3111+ if basedir != self.basedir:
3112+ self.basedir = basedir
3113+
3114+ def close(self):
3115+ "Close an archive, save its state, and update any changed archives."
3116+ self.update_dirty_archives()
3117+ self.update_TOC = 0
3118+ self.write_TOC()
3119+ # Save the collective state
3120+ self.message(C_('Pickling archive state into ')
3121+ + os.path.join(self.basedir, 'pipermail.pck'))
3122+ self.database.close()
3123+ del self.database
3124+
3125+ omask = os.umask(007)
3126+ try:
3127+ f = open(os.path.join(self.basedir, 'pipermail.pck'), 'w')
3128+ finally:
3129+ os.umask(omask)
3130+ pickle.dump(self.getstate(), f)
3131+ f.close()
3132+
3133+ def getstate(self):
3134+ # can override this in subclass
3135+ return self.__dict__
3136+
3137+ #
3138+ # Private methods
3139+ #
3140+ # These will be neither overridden nor called by custom archivers.
3141+ #
3142+
3143+
3144+ # Create a dictionary of various parameters that will be passed
3145+ # to the write_index_{header,footer} functions
3146+ def __set_parameters(self, archive):
3147+ # Determine the earliest and latest date in the archive
3148+ firstdate = self.database.firstdate(archive)
3149+ lastdate = self.database.lastdate(archive)
3150+
3151+ # Get the current time
3152+ now = time.asctime(time.localtime(time.time()))
3153+ self.firstdate = firstdate
3154+ self.lastdate = lastdate
3155+ self.archivedate = now
3156+ self.size = self.database.numArticles(archive)
3157+ self.archive = archive
3158+ self.version = __version__
3159+
3160+ # Find the message ID of an article's parent, or return None
3161+ # if no parent can be found.
3162+
3163+ def __findParent(self, article, children = []):
3164+ parentID = None
3165+ if article.in_reply_to:
3166+ parentID = article.in_reply_to
3167+ elif article.references:
3168+ # Remove article IDs that aren't in the archive
3169+ refs = filter(self.articleIndex.has_key, article.references)
3170+ if not refs:
3171+ return None
3172+ maxdate = self.database.getArticle(self.archive,
3173+ refs[0])
3174+ for ref in refs[1:]:
3175+ a = self.database.getArticle(self.archive, ref)
3176+ if a.date > maxdate.date:
3177+ maxdate = a
3178+ parentID = maxdate.msgid
3179+ else:
3180+ # Look for the oldest matching subject
3181+ try:
3182+ key, tempid = \
3183+ self.subjectIndex.set_location(article.subject)
3184+ print key, tempid
3185+ self.subjectIndex.next()
3186+ [subject, date] = key.split('\0')
3187+ print article.subject, subject, date
3188+ if subject == article.subject and tempid not in children:
3189+ parentID = tempid
3190+ except KeyError:
3191+ pass
3192+ return parentID
3193+
3194+ # Update the threaded index completely
3195+ def updateThreadedIndex(self):
3196+ # Erase the threaded index
3197+ self.database.clearIndex(self.archive, 'thread')
3198+
3199+ # Loop over all the articles
3200+ msgid = self.database.first(self.archive, 'date')
3201+ while msgid is not None:
3202+ try:
3203+ article = self.database.getArticle(self.archive, msgid)
3204+ except KeyError:
3205+ pass
3206+ else:
3207+ if article.parentID is None or \
3208+ not self.database.hasArticle(self.archive,
3209+ article.parentID):
3210+ # then
3211+ pass
3212+ else:
3213+ parent = self.database.getArticle(self.archive,
3214+ article.parentID)
3215+ article.threadKey = (parent.threadKey + article.date + '.'
3216+ + str(article.sequence) + '-')
3217+ self.database.setThreadKey(self.archive,
3218+ (article.threadKey, article.msgid),
3219+ msgid)
3220+ msgid = self.database.next(self.archive, 'date')
3221+
3222+ #
3223+ # Public methods:
3224+ #
3225+ # These are part of the public interface of the T class, but will
3226+ # never be overridden (unless you're trying to do something very new).
3227+
3228+ # Update a single archive's indices, whether the archive's been
3229+ # dirtied or not.
3230+ def update_archive(self, archive):
3231+ self.archive = archive
3232+ self.message(C_("Updating index files for archive [%(archive)s]"))
3233+ arcdir = os.path.join(self.basedir, archive)
3234+ self.__set_parameters(archive)
3235+
3236+ for hdr in ('Date', 'Subject', 'Author'):
3237+ self._update_simple_index(hdr, archive, arcdir)
3238+
3239+ self._update_thread_index(archive, arcdir)
3240+
3241+ def _update_simple_index(self, hdr, archive, arcdir):
3242+ self.message(" " + hdr)
3243+ self.type = hdr
3244+ hdr = hdr.lower()
3245+
3246+ self._open_index_file_as_stdout(arcdir, hdr)
3247+ self.write_index_header()
3248+ count = 0
3249+ # Loop over the index entries
3250+ msgid = self.database.first(archive, hdr)
3251+ while msgid is not None:
3252+ try:
3253+ article = self.database.getArticle(self.archive, msgid)
3254+ except KeyError:
3255+ pass
3256+ else:
3257+ count = count + 1
3258+ self.write_index_entry(article)
3259+ msgid = self.database.next(archive, hdr)
3260+ # Finish up this index
3261+ self.write_index_footer()
3262+ self._restore_stdout()
3263+
3264+ def _update_thread_index(self, archive, arcdir):
3265+ self.message(C_(" Thread"))
3266+ self._open_index_file_as_stdout(arcdir, "thread")
3267+ self.type = 'Thread'
3268+ self.write_index_header()
3269+
3270+ # To handle the prev./next in thread pointers, we need to
3271+ # track articles 5 at a time.
3272+
3273+ # Get the first 5 articles
3274+ L = [None] * 5
3275+ i = 2
3276+ msgid = self.database.first(self.archive, 'thread')
3277+
3278+ while msgid is not None and i < 5:
3279+ L[i] = self.database.getArticle(self.archive, msgid)
3280+ i = i + 1
3281+ msgid = self.database.next(self.archive, 'thread')
3282+
3283+ while L[2] is not None:
3284+ article = L[2]
3285+ artkey = None
3286+ if article is not None:
3287+ artkey = article.threadKey
3288+ if artkey is not None:
3289+ self.write_threadindex_entry(article, artkey.count('-') - 1)
3290+ if self.database.changed.has_key((archive,article.msgid)):
3291+ a1 = L[1]
3292+ a3 = L[3]
3293+ self.update_article(arcdir, article, a1, a3)
3294+ if a3 is not None:
3295+ self.database.changed[(archive, a3.msgid)] = None
3296+ if a1 is not None:
3297+ key = archive, a1.msgid
3298+ if not self.database.changed.has_key(key):
3299+ self.update_article(arcdir, a1, L[0], L[2])
3300+ else:
3301+ del self.database.changed[key]
3302+ if L[0]:
3303+ L[0].finished_update_article()
3304+ L = L[1:] # Rotate the list
3305+ if msgid is None:
3306+ L.append(msgid)
3307+ else:
3308+ L.append(self.database.getArticle(self.archive, msgid))
3309+ msgid = self.database.next(self.archive, 'thread')
3310+
3311+ self.write_index_footer()
3312+ self._restore_stdout()
3313+
3314+ def _open_index_file_as_stdout(self, arcdir, index_name):
3315+ path = os.path.join(arcdir, index_name + self.INDEX_EXT)
3316+ omask = os.umask(002)
3317+ try:
3318+ self.__f = open(path, 'w')
3319+ finally:
3320+ os.umask(omask)
3321+ self.__stdout = sys.stdout
3322+ sys.stdout = self.__f
3323+
3324+ def _restore_stdout(self):
3325+ sys.stdout = self.__stdout
3326+ self.__f.close()
3327+ del self.__f
3328+ del self.__stdout
3329+
3330+ # Update only archives that have been marked as "changed".
3331+ def update_dirty_archives(self):
3332+ for i in self._dirty_archives:
3333+ self.update_archive(i)
3334+ self._dirty_archives = []
3335+
3336+ # Read a Unix mailbox file from the file object <input>,
3337+ # and create a series of Article objects. Each article
3338+ # object will then be archived.
3339+
3340+ def _makeArticle(self, msg, sequence):
3341+ return Article(msg, sequence)
3342+
3343+ def processUnixMailbox(self, input, start=None, end=None):
3344+ mbox = ArchiverMailbox(input, self.maillist)
3345+ if start is None:
3346+ start = 0
3347+ counter = 0
3348+ if start:
3349+ mbox.skipping(True)
3350+ while counter < start:
3351+ try:
3352+ m = mbox.next()
3353+ except Errors.DiscardMessage:
3354+ continue
3355+ if m is None:
3356+ return
3357+ counter += 1
3358+ if start:
3359+ mbox.skipping(False)
3360+ while 1:
3361+ try:
3362+ pos = input.tell()
3363+ m = mbox.next()
3364+ except Errors.DiscardMessage:
3365+ continue
3366+ except Exception:
3367+ syslog('error', 'uncaught archiver exception at filepos: %s',
3368+ pos)
3369+ raise
3370+ if m is None:
3371+ break
3372+ if m == '':
3373+ # It was an unparseable message
3374+ continue
3375+ msgid = m.get('message-id', 'n/a')
3376+ self.message(C_('#%(counter)05d %(msgid)s'))
3377+ a = self._makeArticle(m, self.sequence)
3378+ self.sequence += 1
3379+ self.add_article(a)
3380+ if end is not None and counter >= end:
3381+ break
3382+ counter += 1
3383+
3384+ def new_archive(self, archive, archivedir):
3385+ self.archives.append(archive)
3386+ self.update_TOC = 1
3387+ self.database.newArchive(archive)
3388+ # If the archive directory doesn't exist, create it
3389+ try:
3390+ os.stat(archivedir)
3391+ except os.error, errdata:
3392+ errno, errmsg = errdata
3393+ if errno == 2:
3394+ omask = os.umask(0)
3395+ try:
3396+ os.mkdir(archivedir, self.DIRMODE)
3397+ finally:
3398+ os.umask(omask)
3399+ else:
3400+ raise os.error, errdata
3401+ self.open_new_archive(archive, archivedir)
3402+
3403+ def add_article(self, article):
3404+ archives = self.get_archives(article)
3405+ if not archives:
3406+ return
3407+ if type(archives) == type(''):
3408+ archives = [archives]
3409+
3410+ article.filename = filename = self.get_filename(article)
3411+ temp = self.format_article(article)
3412+ for arch in archives:
3413+ self.archive = arch # why do this???
3414+ archivedir = os.path.join(self.basedir, arch)
3415+ if arch not in self.archives:
3416+ self.new_archive(arch, archivedir)
3417+
3418+ # Write the HTML-ized article
3419+ self.write_article(arch, temp, os.path.join(archivedir,
3420+ filename))
3421+
3422+ if article.decoded.has_key('author'):
3423+ author = fixAuthor(article.decoded['author'])
3424+ else:
3425+ author = fixAuthor(article.author)
3426+ if article.decoded.has_key('stripped'):
3427+ subject = article.decoded['stripped'].lower()
3428+ else:
3429+ subject = article.subject.lower()
3430+
3431+ article.parentID = parentID = self.get_parent_info(arch, article)
3432+ if parentID:
3433+ parent = self.database.getArticle(arch, parentID)
3434+ article.threadKey = (parent.threadKey + article.date + '.'
3435+ + str(article.sequence) + '-')
3436+ else:
3437+ article.threadKey = (article.date + '.'
3438+ + str(article.sequence) + '-')
3439+ key = article.threadKey, article.msgid
3440+
3441+ self.database.setThreadKey(arch, key, article.msgid)
3442+ self.database.addArticle(arch, temp, author=author,
3443+ subject=subject)
3444+
3445+ if arch not in self._dirty_archives:
3446+ self._dirty_archives.append(arch)
3447+
3448+ def get_parent_info(self, archive, article):
3449+ parentID = None
3450+ if article.in_reply_to:
3451+ if self.database.hasArticle(archive, article.in_reply_to):
3452+ # Only use In-Reply-To if it's in the archive.
3453+ parentID = article.in_reply_to
3454+ if not parentID and article.references:
3455+ refs = self._remove_external_references(article.references)
3456+ if refs:
3457+ maxdate = self.database.getArticle(archive, refs[0])
3458+ for ref in refs[1:]:
3459+ a = self.database.getArticle(archive, ref)
3460+ if a.date > maxdate.date:
3461+ maxdate = a
3462+ parentID = maxdate.msgid
3463+ if not parentID:
3464+ # Get the oldest article with a matching subject, and
3465+ # assume this is a follow-up to that article
3466+ # But, use the subject that's in the database
3467+ if article.decoded.has_key('stripped'):
3468+ subject = article.decoded['stripped'].lower()
3469+ else:
3470+ subject = article.subject.lower()
3471+ parentID = self.database.getOldestArticle(archive, subject)
3472+
3473+ if parentID and not self.database.hasArticle(archive, parentID):
3474+ parentID = None
3475+ return parentID
3476+
3477+ def write_article(self, index, article, path):
3478+ omask = os.umask(002)
3479+ try:
3480+ f = open(path, 'w')
3481+ finally:
3482+ os.umask(omask)
3483+ temp_stdout, sys.stdout = sys.stdout, f
3484+ self.write_article_header(article)
3485+ sys.stdout.writelines(article.body)
3486+ self.write_article_footer(article)
3487+ sys.stdout = temp_stdout
3488+ f.close()
3489+
3490+ def _remove_external_references(self, refs):
3491+ keep = []
3492+ for ref in refs:
3493+ if self.database.hasArticle(self.archive, ref):
3494+ keep.append(ref)
3495+ return keep
3496+
3497+ # Abstract methods: these will need to be overridden by subclasses
3498+ # before anything useful can be done.
3499+
3500+ def get_filename(self, article):
3501+ pass
3502+ def get_archives(self, article):
3503+ """Return a list of indexes where the article should be filed.
3504+ A string can be returned if the list only contains one entry,
3505+ and the empty list is legal."""
3506+ pass
3507+ def format_article(self, article):
3508+ pass
3509+ def write_index_header(self):
3510+ pass
3511+ def write_index_footer(self):
3512+ pass
3513+ def write_index_entry(self, article):
3514+ pass
3515+ def write_threadindex_entry(self, article, depth):
3516+ pass
3517+ def write_article_header(self, article):
3518+ pass
3519+ def write_article_footer(self, article):
3520+ pass
3521+ def write_article_entry(self, article):
3522+ pass
3523+ def update_article(self, archivedir, article, prev, next):
3524+ pass
3525+ def write_TOC(self):
3526+ pass
3527+ def open_new_archive(self, archive, dir):
3528+ pass
3529+ def message(self, msg):
3530+ pass
3531+
3532+
3533+class BSDDBdatabase(Database):
3534+ __super_addArticle = Database.addArticle
3535+
3536+ def __init__(self, basedir):
3537+ self.__cachekeys = []
3538+ self.__cachedict = {}
3539+ self.__currentOpenArchive = None # The currently open indices
3540+ self.basedir = os.path.expanduser(basedir)
3541+ self.changed = {} # Recently added articles, indexed only by
3542+ # message ID
3543+
3544+ def firstdate(self, archive):
3545+ self.__openIndices(archive)
3546+ date = 'None'
3547+ try:
3548+ date, msgid = self.dateIndex.first()
3549+ date = time.asctime(time.localtime(float(date)))
3550+ except KeyError:
3551+ pass
3552+ return date
3553+
3554+ def lastdate(self, archive):
3555+ self.__openIndices(archive)
3556+ date = 'None'
3557+ try:
3558+ date, msgid = self.dateIndex.last()
3559+ date = time.asctime(time.localtime(float(date)))
3560+ except KeyError:
3561+ pass
3562+ return date
3563+
3564+ def numArticles(self, archive):
3565+ self.__openIndices(archive)
3566+ return len(self.dateIndex)
3567+
3568+ def addArticle(self, archive, article, subject=None, author=None,
3569+ date=None):
3570+ self.__openIndices(archive)
3571+ self.__super_addArticle(archive, article, subject, author, date)
3572+
3573+ # Open the BSDDB files that are being used as indices
3574+ # (dateIndex, authorIndex, subjectIndex, articleIndex)
3575+ def __openIndices(self, archive):
3576+ if self.__currentOpenArchive == archive:
3577+ return
3578+
3579+ import bsddb
3580+ self.__closeIndices()
3581+ arcdir = os.path.join(self.basedir, 'database')
3582+ omask = os.umask(0)
3583+ try:
3584+ try:
3585+ os.mkdir(arcdir, 02775)
3586+ except OSError:
3587+ # BAW: Hmm...
3588+ pass
3589+ finally:
3590+ os.umask(omask)
3591+ for hdr in ('date', 'author', 'subject', 'article', 'thread'):
3592+ path = os.path.join(arcdir, archive + '-' + hdr)
3593+ t = bsddb.btopen(path, 'c')
3594+ setattr(self, hdr + 'Index', t)
3595+ self.__currentOpenArchive = archive
3596+
3597+ # Close the BSDDB files that are being used as indices (if they're
3598+ # open--this is safe to call if they're already closed)
3599+ def __closeIndices(self):
3600+ if self.__currentOpenArchive is not None:
3601+ pass
3602+ for hdr in ('date', 'author', 'subject', 'thread', 'article'):
3603+ attr = hdr + 'Index'
3604+ if hasattr(self, attr):
3605+ index = getattr(self, attr)
3606+ if hdr == 'article':
3607+ if not hasattr(self, 'archive_length'):
3608+ self.archive_length = {}
3609+ self.archive_length[self.__currentOpenArchive] = len(index)
3610+ index.close()
3611+ delattr(self,attr)
3612+ self.__currentOpenArchive = None
3613+
3614+ def close(self):
3615+ self.__closeIndices()
3616+ def hasArticle(self, archive, msgid):
3617+ self.__openIndices(archive)
3618+ return self.articleIndex.has_key(msgid)
3619+ def setThreadKey(self, archive, key, msgid):
3620+ self.__openIndices(archive)
3621+ self.threadIndex[key] = msgid
3622+ def getArticle(self, archive, msgid):
3623+ self.__openIndices(archive)
3624+ if self.__cachedict.has_key(msgid):
3625+ self.__cachekeys.remove(msgid)
3626+ self.__cachekeys.append(msgid)
3627+ return self.__cachedict[msgid]
3628+ if len(self.__cachekeys) == CACHESIZE:
3629+ delkey, self.__cachekeys = (self.__cachekeys[0],
3630+ self.__cachekeys[1:])
3631+ del self.__cachedict[delkey]
3632+ s = self.articleIndex[msgid]
3633+ article = pickle.loads(s)
3634+ self.__cachekeys.append(msgid)
3635+ self.__cachedict[msgid] = article
3636+ return article
3637+
3638+ def first(self, archive, index):
3639+ self.__openIndices(archive)
3640+ index = getattr(self, index+'Index')
3641+ try:
3642+ key, msgid = index.first()
3643+ return msgid
3644+ except KeyError:
3645+ return None
3646+ def next(self, archive, index):
3647+ self.__openIndices(archive)
3648+ index = getattr(self, index+'Index')
3649+ try:
3650+ key, msgid = index.next()
3651+ except KeyError:
3652+ return None
3653+ else:
3654+ return msgid
3655+
3656+ def getOldestArticle(self, archive, subject):
3657+ self.__openIndices(archive)
3658+ subject = subject.lower()
3659+ try:
3660+ key, tempid = self.subjectIndex.set_location(subject)
3661+ self.subjectIndex.next()
3662+ [subject2, date] = key.split('\0')
3663+ if subject != subject2:
3664+ return None
3665+ return tempid
3666+ except KeyError: # XXX what line raises the KeyError?
3667+ return None
3668+
3669+ def newArchive(self, archive):
3670+ pass
3671+
3672+ def clearIndex(self, archive, index):
3673+ self.__openIndices(archive)
3674+ index = getattr(self, index+'Index')
3675+ finished = 0
3676+ try:
3677+ key, msgid = self.threadIndex.first()
3678+ except KeyError:
3679+ finished = 1
3680+ while not finished:
3681+ del self.threadIndex[key]
3682+ try:
3683+ key, msgid = self.threadIndex.next()
3684+ except KeyError:
3685+ finished = 1
3686+
3687+
3688
3689=== added file 'Mailman/Autoresponder.py'
3690--- Mailman/Autoresponder.py 1970-01-01 00:00:00 +0000
3691+++ Mailman/Autoresponder.py 2025-02-01 12:21:11 +0000
3692@@ -0,0 +1,43 @@
3693+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
3694+#
3695+# This program is free software; you can redistribute it and/or
3696+# modify it under the terms of the GNU General Public License
3697+# as published by the Free Software Foundation; either version 2
3698+# of the License, or (at your option) any later version.
3699+#
3700+# This program is distributed in the hope that it will be useful,
3701+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3702+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3703+# GNU General Public License for more details.
3704+#
3705+# You should have received a copy of the GNU General Public License
3706+# along with this program; if not, write to the Free Software
3707+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
3708+
3709+"""MailList mixin class managing the autoresponder.
3710+"""
3711+
3712+from Mailman import mm_cfg
3713+from Mailman.i18n import _
3714+
3715+
3716+
3717
3718+class Autoresponder:
3719+ def InitVars(self):
3720+ # configurable
3721+ self.autorespond_postings = 0
3722+ self.autorespond_admin = 0
3723+ # this value can be
3724+ # 0 - no autoresponse on the -request line
3725+ # 1 - autorespond, but discard the original message
3726+ # 2 - autorespond, and forward the message on to be processed
3727+ self.autorespond_requests = 0
3728+ self.autoresponse_postings_text = ''
3729+ self.autoresponse_admin_text = ''
3730+ self.autoresponse_request_text = ''
3731+ self.autoresponse_graceperiod = 90 # days
3732+ # non-configurable
3733+ self.postings_responses = {}
3734+ self.admin_responses = {}
3735+ self.request_responses = {}
3736+
3737
3738=== added file 'Mailman/Bouncer.py'
3739--- Mailman/Bouncer.py 1970-01-01 00:00:00 +0000
3740+++ Mailman/Bouncer.py 2025-02-01 12:21:11 +0000
3741@@ -0,0 +1,354 @@
3742+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
3743+#
3744+# This program is free software; you can redistribute it and/or
3745+# modify it under the terms of the GNU General Public License
3746+# as published by the Free Software Foundation; either version 2
3747+# of the License, or (at your option) any later version.
3748+#
3749+# This program is distributed in the hope that it will be useful,
3750+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3751+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3752+# GNU General Public License for more details.
3753+#
3754+# You should have received a copy of the GNU General Public License
3755+# along with this program; if not, write to the Free Software
3756+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
3757+# USA.
3758+
3759+"""Handle delivery bounces."""
3760+
3761+import sys
3762+import time
3763+from types import StringType
3764+
3765+from email.MIMEText import MIMEText
3766+from email.MIMEMessage import MIMEMessage
3767+
3768+from Mailman import mm_cfg
3769+from Mailman import Utils
3770+from Mailman import Message
3771+from Mailman import MemberAdaptor
3772+from Mailman import Pending
3773+from Mailman.Errors import MMUnknownListError
3774+from Mailman.Logging.Syslog import syslog
3775+from Mailman import i18n
3776+
3777+EMPTYSTRING = ''
3778+
3779+# This constant is supposed to represent the day containing the first midnight
3780+# after the epoch. We'll add (0,)*6 to this tuple to get a value appropriate
3781+# for time.mktime().
3782+ZEROHOUR_PLUSONEDAY = time.localtime(mm_cfg.days(1))[:3]
3783+
3784+def D_(s): return s
3785+_ = D_
3786+
3787+REASONS = {MemberAdaptor.BYBOUNCE: _('due to excessive bounces'),
3788+ MemberAdaptor.BYUSER: _('by yourself'),
3789+ MemberAdaptor.BYADMIN: _('by the list administrator'),
3790+ MemberAdaptor.UNKNOWN: _('for unknown reasons'),
3791+ }
3792+
3793+_ = i18n._
3794+
3795+
3796+
3797
3798+class _BounceInfo:
3799+ def __init__(self, member, score, date, noticesleft):
3800+ self.member = member
3801+ self.cookie = None
3802+ self.reset(score, date, noticesleft)
3803+
3804+ def reset(self, score, date, noticesleft):
3805+ self.score = score
3806+ self.date = date
3807+ self.noticesleft = noticesleft
3808+ self.lastnotice = ZEROHOUR_PLUSONEDAY
3809+
3810+ def __repr__(self):
3811+ # For debugging
3812+ return """\
3813+<bounce info for member %(member)s
3814+ current score: %(score)s
3815+ last bounce date: %(date)s
3816+ email notices left: %(noticesleft)s
3817+ last notice date: %(lastnotice)s
3818+ confirmation cookie: %(cookie)s
3819+ >""" % self.__dict__
3820+
3821+
3822+
3823
3824+class Bouncer:
3825+ def InitVars(self):
3826+ # Configurable...
3827+ self.bounce_processing = mm_cfg.DEFAULT_BOUNCE_PROCESSING
3828+ self.bounce_score_threshold = mm_cfg.DEFAULT_BOUNCE_SCORE_THRESHOLD
3829+ self.bounce_info_stale_after = mm_cfg.DEFAULT_BOUNCE_INFO_STALE_AFTER
3830+ self.bounce_you_are_disabled_warnings = \
3831+ mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS
3832+ self.bounce_you_are_disabled_warnings_interval = \
3833+ mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL
3834+ self.bounce_unrecognized_goes_to_list_owner = \
3835+ mm_cfg.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER
3836+ self.bounce_notify_owner_on_bounce_increment = \
3837+ mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_BOUNCE_INCREMENT
3838+ self.bounce_notify_owner_on_disable = \
3839+ mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE
3840+ self.bounce_notify_owner_on_removal = \
3841+ mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL
3842+ # Not configurable...
3843+ #
3844+ # This holds legacy member related information. It's keyed by the
3845+ # member address, and the value is an object containing the bounce
3846+ # score, the date of the last received bounce, and a count of the
3847+ # notifications left to send.
3848+ self.bounce_info = {}
3849+ # New style delivery status
3850+ self.delivery_status = {}
3851+
3852+ def registerBounce(self, member, msg, weight=1.0, day=None, sibling=False):
3853+ if not self.isMember(member):
3854+ # check regular_include_lists, only one level
3855+ if not self.regular_include_lists or sibling:
3856+ return
3857+ from Mailman.MailList import MailList
3858+ for listaddr in self.regular_include_lists:
3859+ listname, hostname = listaddr.split('@')
3860+ listname = listname.lower()
3861+ if listname == self.internal_name():
3862+ syslog('error',
3863+ 'Bouncer: %s: Include list self reference',
3864+ listname)
3865+ continue
3866+ try:
3867+ siblist = None
3868+ try:
3869+ siblist = MailList(listname)
3870+ except MMUnknownListError:
3871+ syslog('error',
3872+ 'Bouncer: %s: Include list "%s" not found.',
3873+ self.real_name,
3874+ listname)
3875+ continue
3876+ siblist.registerBounce(member, msg, weight, day,
3877+ sibling=True)
3878+ siblist.Save()
3879+ finally:
3880+ if siblist and siblist.Locked():
3881+ siblist.Unlock()
3882+ return
3883+ info = self.getBounceInfo(member)
3884+ first_today = True
3885+ if day is None:
3886+ # Use today's date
3887+ day = time.localtime()[:3]
3888+ if not isinstance(info, _BounceInfo):
3889+ # This is the first bounce we've seen from this member
3890+ info = _BounceInfo(member, weight, day,
3891+ self.bounce_you_are_disabled_warnings)
3892+ # setBounceInfo is now called below after check phase.
3893+ syslog('bounce', '%s: %s bounce score: %s', self.internal_name(),
3894+ member, info.score)
3895+ # Continue to the check phase below
3896+ elif self.getDeliveryStatus(member) <> MemberAdaptor.ENABLED:
3897+ # The user is already disabled, so we can just ignore subsequent
3898+ # bounces. These are likely due to residual messages that were
3899+ # sent before disabling the member, but took a while to bounce.
3900+ syslog('bounce', '%s: %s residual bounce received',
3901+ self.internal_name(), member)
3902+ return
3903+ elif info.date == day:
3904+ # We've already scored any bounces for this day, so ignore it.
3905+ first_today = False
3906+ syslog('bounce', '%s: %s already scored a bounce for date %s',
3907+ self.internal_name(), member,
3908+ time.strftime('%d-%b-%Y', day + (0,0,0,0,1,0)))
3909+ # Continue to check phase below
3910+ else:
3911+ # See if this member's bounce information is stale.
3912+ now = Utils.midnight(day)
3913+ lastbounce = Utils.midnight(info.date)
3914+ if lastbounce + self.bounce_info_stale_after < now:
3915+ # Information is stale, so simply reset it
3916+ info.reset(weight, day, self.bounce_you_are_disabled_warnings)
3917+ syslog('bounce', '%s: %s has stale bounce info, resetting',
3918+ self.internal_name(), member)
3919+ else:
3920+ # Nope, the information isn't stale, so add to the bounce
3921+ # score and take any necessary action.
3922+ info.score += weight
3923+ info.date = day
3924+ syslog('bounce', '%s: %s current bounce score: %s',
3925+ self.internal_name(), member, info.score)
3926+ # Continue to the check phase below
3927+ #
3928+ # Now that we've adjusted the bounce score for this bounce, let's
3929+ # check to see if the disable-by-bounce threshold has been reached.
3930+ if info.score >= self.bounce_score_threshold:
3931+ if mm_cfg.VERP_PROBES:
3932+ syslog('bounce',
3933+ 'sending %s list probe to: %s (score %s >= %s)',
3934+ self.internal_name(), member, info.score,
3935+ self.bounce_score_threshold)
3936+ self.sendProbe(member, msg)
3937+ info.reset(0, info.date, info.noticesleft)
3938+ else:
3939+ self.disableBouncingMember(member, info, msg)
3940+ elif self.bounce_notify_owner_on_bounce_increment and first_today:
3941+ self.__sendAdminBounceNotice(member, msg,
3942+ did=_('bounce score incremented'))
3943+ # We've set/changed bounce info above. We now need to tell the
3944+ # MemberAdaptor to set/update it. We do it here in case the
3945+ # MemberAdaptor stores bounce info externally to the list object to
3946+ # be sure updated information is stored, but we have to be sure the
3947+ # member wasn't removed.
3948+ if self.isMember(member):
3949+ self.setBounceInfo(member, info)
3950+
3951+ def disableBouncingMember(self, member, info, msg):
3952+ # Initialize their confirmation cookie. If we do it when we get the
3953+ # first bounce, it'll expire by the time we get the disabling bounce.
3954+ cookie = self.pend_new(Pending.RE_ENABLE, self.internal_name(), member)
3955+ info.cookie = cookie
3956+ # In case the MemberAdaptor stores bounce info externally to
3957+ # the list, we need to tell it to save the cookie
3958+ self.setBounceInfo(member, info)
3959+ # Disable them
3960+ if mm_cfg.VERP_PROBES:
3961+ syslog('bounce', '%s: %s disabling due to probe bounce received',
3962+ self.internal_name(), member)
3963+ else:
3964+ syslog('bounce', '%s: %s disabling due to bounce score %s >= %s',
3965+ self.internal_name(), member,
3966+ info.score, self.bounce_score_threshold)
3967+ self.setDeliveryStatus(member, MemberAdaptor.BYBOUNCE)
3968+ self.sendNextNotification(member)
3969+ if self.bounce_notify_owner_on_disable:
3970+ self.__sendAdminBounceNotice(member, msg)
3971+
3972+ def __sendAdminBounceNotice(self, member, msg, did=None):
3973+ # BAW: This is a bit kludgey, but we're not providing as much
3974+ # information in the new admin bounce notices as we used to (some of
3975+ # it was of dubious value). However, we'll provide empty, strange, or
3976+ # meaningless strings for the unused %()s fields so that the language
3977+ # translators don't have to provide new templates.
3978+ if did is None:
3979+ did = _('disabled')
3980+ siteowner = Utils.get_site_email(self.host_name)
3981+ text = Utils.maketext(
3982+ 'bounce.txt',
3983+ {'listname' : self.real_name,
3984+ 'addr' : member,
3985+ 'negative' : '',
3986+ 'did' : did,
3987+ 'but' : '',
3988+ 'reenable' : '',
3989+ 'owneraddr': siteowner,
3990+ }, mlist=self)
3991+ subject = _('Bounce action notification')
3992+ umsg = Message.UserNotification(self.GetOwnerEmail(),
3993+ siteowner, subject,
3994+ lang=self.preferred_language)
3995+ # BAW: Be sure you set the type before trying to attach, or you'll get
3996+ # a MultipartConversionError.
3997+ umsg.set_type('multipart/mixed')
3998+ umsg.attach(
3999+ MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language)))
4000+ if isinstance(msg, StringType):
4001+ umsg.attach(MIMEText(msg))
4002+ else:
4003+ umsg.attach(MIMEMessage(msg))
4004+ umsg.send(self)
4005+
4006+ def sendNextNotification(self, member):
4007+ global _
4008+ info = self.getBounceInfo(member)
4009+ if info is None:
4010+ return
4011+ reason = self.getDeliveryStatus(member)
4012+ if info.noticesleft <= 0:
4013+ # BAW: Remove them now, with a notification message
4014+ _ = D_
4015+ self.ApprovedDeleteMember(
4016+ member, _('disabled address'),
4017+ admin_notif=self.bounce_notify_owner_on_removal,
4018+ userack=1)
4019+ _ = i18n._
4020+ # Expunge the pending cookie for the user. We throw away the
4021+ # returned data.
4022+ self.pend_confirm(info.cookie)
4023+ if reason == MemberAdaptor.BYBOUNCE:
4024+ syslog('bounce', '%s: %s deleted after exhausting notices',
4025+ self.internal_name(), member)
4026+ syslog('subscribe', '%s: %s auto-unsubscribed [reason: %s]',
4027+ self.internal_name(), member,
4028+ {MemberAdaptor.BYBOUNCE: 'BYBOUNCE',
4029+ MemberAdaptor.BYUSER: 'BYUSER',
4030+ MemberAdaptor.BYADMIN: 'BYADMIN',
4031+ MemberAdaptor.UNKNOWN: 'UNKNOWN'}.get(
4032+ reason, 'invalid value'))
4033+ return
4034+ # Send the next notification
4035+ confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
4036+ info.cookie)
4037+ optionsurl = self.GetOptionsURL(member, absolute=1)
4038+ reqaddr = self.GetRequestEmail()
4039+ lang = self.getMemberLanguage(member)
4040+ txtreason = REASONS.get(reason)
4041+ if txtreason is None:
4042+ txtreason = _('for unknown reasons')
4043+ else:
4044+ txtreason = _(txtreason)
4045+ # Give a little bit more detail on bounce disables
4046+ if reason == MemberAdaptor.BYBOUNCE:
4047+ date = time.strftime('%d-%b-%Y',
4048+ time.localtime(Utils.midnight(info.date)))
4049+ extra = _(' The last bounce received from you was dated %(date)s')
4050+ txtreason += extra
4051+ text = Utils.maketext(
4052+ 'disabled.txt',
4053+ {'listname' : self.real_name,
4054+ 'noticesleft': info.noticesleft,
4055+ 'confirmurl' : confirmurl,
4056+ 'optionsurl' : optionsurl,
4057+ 'password' : self.getMemberPassword(member),
4058+ 'owneraddr' : self.GetOwnerEmail(),
4059+ 'reason' : txtreason,
4060+ }, lang=lang, mlist=self)
4061+ msg = Message.UserNotification(member, reqaddr, text=text, lang=lang)
4062+ # BAW: See the comment in MailList.py ChangeMemberAddress() for why we
4063+ # set the Subject this way.
4064+ del msg['subject']
4065+ msg['Subject'] = 'confirm ' + info.cookie
4066+ # Send without Precedence: bulk. Bug #808821.
4067+ msg.send(self, noprecedence=True)
4068+ info.noticesleft -= 1
4069+ info.lastnotice = time.localtime()[:3]
4070+ # In case the MemberAdaptor stores bounce info externally to
4071+ # the list, we need to tell it to update
4072+ self.setBounceInfo(member, info)
4073+
4074+ def BounceMessage(self, msg, msgdata, e=None):
4075+ # Bounce a message back to the sender, with an error message if
4076+ # provided in the exception argument.
4077+ sender = msg.get_sender()
4078+ subject = msg.get('subject', _('(no subject)'))
4079+ subject = Utils.oneline(subject,
4080+ Utils.GetCharSet(self.preferred_language))
4081+ if e is None:
4082+ notice = _('[No bounce details are available]')
4083+ else:
4084+ notice = _(e.notice())
4085+ # Currently we always craft bounces as MIME messages.
4086+ bmsg = Message.UserNotification(msg.get_sender(),
4087+ self.GetOwnerEmail(),
4088+ subject,
4089+ lang=self.preferred_language)
4090+ # BAW: Be sure you set the type before trying to attach, or you'll get
4091+ # a MultipartConversionError.
4092+ bmsg.set_type('multipart/mixed')
4093+ txt = MIMEText(notice,
4094+ _charset=Utils.GetCharSet(self.preferred_language))
4095+ bmsg.attach(txt)
4096+ bmsg.attach(MIMEMessage(msg))
4097+ bmsg.send(self)
4098
4099=== added directory 'Mailman/Bouncers'
4100=== added file 'Mailman/Bouncers/AOL.py'
4101--- Mailman/Bouncers/AOL.py 1970-01-01 00:00:00 +0000
4102+++ Mailman/Bouncers/AOL.py 2025-02-01 12:21:11 +0000
4103@@ -0,0 +1,45 @@
4104+# Copyright (C) 2009-2018 by the Free Software Foundation, Inc.
4105+#
4106+# This program is free software; you can redistribute it and/or
4107+# modify it under the terms of the GNU General Public License
4108+# as published by the Free Software Foundation; either version 2
4109+# of the License, or (at your option) any later version.
4110+#
4111+# This program is distributed in the hope that it will be useful,
4112+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4113+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4114+# GNU General Public License for more details.
4115+#
4116+# You should have received a copy of the GNU General Public License
4117+# along with this program; if not, write to the Free Software
4118+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
4119+# USA.
4120+
4121+"""Recognizes a class of messages from AOL that report only Screen Name."""
4122+
4123+import re
4124+from email.Utils import parseaddr
4125+
4126+scre = re.compile('mail to the following recipients could not be delivered')
4127+
4128+def process(msg):
4129+ if msg.get_content_type() <> 'text/plain':
4130+ return
4131+ if not parseaddr(msg.get('from', ''))[1].lower().endswith('@aol.com'):
4132+ return
4133+ addrs = []
4134+ found = False
4135+ for line in msg.get_payload(decode=True).splitlines():
4136+ if scre.search(line):
4137+ found = True
4138+ continue
4139+ if found:
4140+ local = line.strip()
4141+ if local:
4142+ if re.search(r'\s', local):
4143+ break
4144+ if re.search('@', local):
4145+ addrs.append(local)
4146+ else:
4147+ addrs.append('%s@aol.com' % local)
4148+ return addrs
4149
4150=== added file 'Mailman/Bouncers/BouncerAPI.py'
4151--- Mailman/Bouncers/BouncerAPI.py 1970-01-01 00:00:00 +0000
4152+++ Mailman/Bouncers/BouncerAPI.py 2025-02-01 12:21:11 +0000
4153@@ -0,0 +1,70 @@
4154+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4155+#
4156+# This program is free software; you can redistribute it and/or
4157+# modify it under the terms of the GNU General Public License
4158+# as published by the Free Software Foundation; either version 2
4159+# of the License, or (at your option) any later version.
4160+#
4161+# This program is distributed in the hope that it will be useful,
4162+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4163+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4164+# GNU General Public License for more details.
4165+#
4166+# You should have received a copy of the GNU General Public License
4167+# along with this program; if not, write to the Free Software
4168+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
4169+# USA.
4170+
4171+"""Contains all the common functionality for msg bounce scanning API.
4172+
4173+This module can also be used as the basis for a bounce detection testing
4174+framework. When run as a script, it expects two arguments, the listname and
4175+the filename containing the bounce message.
4176+
4177+"""
4178+
4179+import sys
4180+
4181+from Mailman.Logging.Syslog import syslog
4182+
4183+# If a bounce detector returns Stop, that means to just discard the message.
4184+# An example is warning messages for temporary delivery problems. These
4185+# shouldn't trigger a bounce notification, but we also don't want to send them
4186+# on to the list administrator.
4187+class _Stop:
4188+ pass
4189+Stop = _Stop()
4190+
4191+
4192+BOUNCE_PIPELINE = [
4193+ 'DSN',
4194+ 'Qmail',
4195+ 'Postfix',
4196+ 'Yahoo',
4197+ 'Caiwireless',
4198+ 'Exchange',
4199+ 'Exim',
4200+ 'Netscape',
4201+ 'Compuserve',
4202+ 'Microsoft',
4203+ 'GroupWise',
4204+ 'SMTP32',
4205+ 'SimpleMatch',
4206+ 'SimpleWarning',
4207+ 'Yale',
4208+ 'LLNL',
4209+ 'AOL',
4210+ ]
4211+
4212+
4213+
4214
4215+# msg must be a mimetools.Message
4216+def ScanMessages(mlist, msg):
4217+ for module in BOUNCE_PIPELINE:
4218+ modname = 'Mailman.Bouncers.' + module
4219+ __import__(modname)
4220+ addrs = sys.modules[modname].process(msg)
4221+ if addrs:
4222+ # Return addrs even if it is Stop. BounceRunner needs this info.
4223+ return addrs
4224+ return []
4225
4226=== added file 'Mailman/Bouncers/Caiwireless.py'
4227--- Mailman/Bouncers/Caiwireless.py 1970-01-01 00:00:00 +0000
4228+++ Mailman/Bouncers/Caiwireless.py 2025-02-01 12:21:11 +0000
4229@@ -0,0 +1,45 @@
4230+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4231+#
4232+# This program is free software; you can redistribute it and/or
4233+# modify it under the terms of the GNU General Public License
4234+# as published by the Free Software Foundation; either version 2
4235+# of the License, or (at your option) any later version.
4236+#
4237+# This program is distributed in the hope that it will be useful,
4238+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4239+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4240+# GNU General Public License for more details.
4241+#
4242+# You should have received a copy of the GNU General Public License
4243+# along with this program; if not, write to the Free Software
4244+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4245+
4246+"""Parse mystery style generated by MTA at caiwireless.net."""
4247+
4248+import re
4249+import email
4250+from cStringIO import StringIO
4251+
4252+tcre = re.compile(r'the following recipients did not receive this message:',
4253+ re.IGNORECASE)
4254+acre = re.compile(r'<(?P<addr>[^>]*)>')
4255+
4256+
4257+
4258
4259+def process(msg):
4260+ if msg.get_content_type() <> 'multipart/mixed':
4261+ return None
4262+ # simple state machine
4263+ # 0 == nothing seen
4264+ # 1 == tag line seen
4265+ state = 0
4266+ # This format thinks it's a MIME, but it really isn't
4267+ for line in email.Iterators.body_line_iterator(msg):
4268+ line = line.strip()
4269+ if state == 0 and tcre.match(line):
4270+ state = 1
4271+ elif state == 1 and line:
4272+ mo = acre.match(line)
4273+ if not mo:
4274+ return None
4275+ return [mo.group('addr')]
4276
4277=== added file 'Mailman/Bouncers/Compuserve.py'
4278--- Mailman/Bouncers/Compuserve.py 1970-01-01 00:00:00 +0000
4279+++ Mailman/Bouncers/Compuserve.py 2025-02-01 12:21:11 +0000
4280@@ -0,0 +1,45 @@
4281+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4282+#
4283+# This program is free software; you can redistribute it and/or
4284+# modify it under the terms of the GNU General Public License
4285+# as published by the Free Software Foundation; either version 2
4286+# of the License, or (at your option) any later version.
4287+#
4288+# This program is distributed in the hope that it will be useful,
4289+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4290+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4291+# GNU General Public License for more details.
4292+#
4293+# You should have received a copy of the GNU General Public License
4294+# along with this program; if not, write to the Free Software
4295+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4296+
4297+"""Compuserve has its own weird format for bounces."""
4298+
4299+import re
4300+import email
4301+
4302+dcre = re.compile(r'your message could not be delivered', re.IGNORECASE)
4303+acre = re.compile(r'Invalid receiver address: (?P<addr>.*)')
4304+
4305+
4306+
4307
4308+def process(msg):
4309+ # simple state machine
4310+ # 0 = nothing seen yet
4311+ # 1 = intro line seen
4312+ state = 0
4313+ addrs = []
4314+ for line in email.Iterators.body_line_iterator(msg):
4315+ if state == 0:
4316+ mo = dcre.search(line)
4317+ if mo:
4318+ state = 1
4319+ elif state == 1:
4320+ mo = dcre.search(line)
4321+ if mo:
4322+ break
4323+ mo = acre.search(line)
4324+ if mo:
4325+ addrs.append(mo.group('addr'))
4326+ return addrs
4327
4328=== added file 'Mailman/Bouncers/DSN.py'
4329--- Mailman/Bouncers/DSN.py 1970-01-01 00:00:00 +0000
4330+++ Mailman/Bouncers/DSN.py 2025-02-01 12:21:11 +0000
4331@@ -0,0 +1,88 @@
4332+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4333+#
4334+# This program is free software; you can redistribute it and/or
4335+# modify it under the terms of the GNU General Public License
4336+# as published by the Free Software Foundation; either version 2
4337+# of the License, or (at your option) any later version.
4338+#
4339+# This program is distributed in the hope that it will be useful,
4340+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4341+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4342+# GNU General Public License for more details.
4343+#
4344+# You should have received a copy of the GNU General Public License
4345+# along with this program; if not, write to the Free Software
4346+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
4347+# USA.
4348+
4349+"""Parse RFC 3464 (i.e. DSN) bounce formats.
4350+
4351+RFC 3464 obsoletes 1894 which was the old DSN standard. This module has not
4352+been audited for differences between the two.
4353+"""
4354+
4355+from email.Iterators import typed_subpart_iterator
4356+from email.Utils import parseaddr
4357+from cStringIO import StringIO
4358+
4359+from Mailman.Bouncers.BouncerAPI import Stop
4360+
4361+try:
4362+ True, False
4363+except NameError:
4364+ True = 1
4365+ False = 0
4366+
4367+
4368+
4369
4370+def process(msg):
4371+ # Iterate over each message/delivery-status subpart
4372+ addrs = []
4373+ for part in typed_subpart_iterator(msg, 'message', 'delivery-status'):
4374+ if not part.is_multipart():
4375+ # Huh?
4376+ continue
4377+ # Each message/delivery-status contains a list of Message objects
4378+ # which are the header blocks. Iterate over those too.
4379+ for msgblock in part.get_payload():
4380+ # We try to dig out the Original-Recipient (which is optional) and
4381+ # Final-Recipient (which is mandatory, but may not exactly match
4382+ # an address on our list). Some MTA's also use X-Actual-Recipient
4383+ # as a synonym for Original-Recipient, but some apparently use
4384+ # that for other purposes :(
4385+ #
4386+ # Also grok out Action so we can do something with that too.
4387+ action = msgblock.get('action', '').lower()
4388+ # Some MTAs have been observed that put comments on the action.
4389+ if action.startswith('delayed'):
4390+ return Stop
4391+ # opensmtpd uses non-compliant Action: error.
4392+ if not (action.startswith('fail') or action.startswith('error')):
4393+ # Some non-permanent failure, so ignore this block
4394+ continue
4395+ params = []
4396+ foundp = False
4397+ for header in ('original-recipient', 'final-recipient'):
4398+ for k, v in msgblock.get_params([], header):
4399+ if k.lower() == 'rfc822':
4400+ foundp = True
4401+ else:
4402+ params.append(k)
4403+ if foundp:
4404+ # Note that params should already be unquoted.
4405+ addrs.extend(params)
4406+ break
4407+ else:
4408+ # MAS: This is a kludge, but SMTP-GATEWAY01.intra.home.dk
4409+ # has a final-recipient with an angle-addr and no
4410+ # address-type parameter at all. Non-compliant, but ...
4411+ for param in params:
4412+ if param.startswith('<') and param.endswith('>'):
4413+ addrs.append(param[1:-1])
4414+ # Uniquify
4415+ rtnaddrs = {}
4416+ for a in addrs:
4417+ if a is not None:
4418+ realname, a = parseaddr(a)
4419+ rtnaddrs[a] = True
4420+ return rtnaddrs.keys()
4421
4422=== added file 'Mailman/Bouncers/Exchange.py'
4423--- Mailman/Bouncers/Exchange.py 1970-01-01 00:00:00 +0000
4424+++ Mailman/Bouncers/Exchange.py 2025-02-01 12:21:11 +0000
4425@@ -0,0 +1,47 @@
4426+# Copyright (C) 2002-2018 by the Free Software Foundation, Inc.
4427+#
4428+# This program is free software; you can redistribute it and/or
4429+# modify it under the terms of the GNU General Public License
4430+# as published by the Free Software Foundation; either version 2
4431+# of the License, or (at your option) any later version.
4432+#
4433+# This program is distributed in the hope that it will be useful,
4434+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4435+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4436+# GNU General Public License for more details.
4437+#
4438+# You should have received a copy of the GNU General Public License
4439+# along with this program; if not, write to the Free Software
4440+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4441+
4442+"""Recognizes (some) Microsoft Exchange formats."""
4443+
4444+import re
4445+import email.Iterators
4446+
4447+scre = re.compile('did not reach the following recipient')
4448+ecre = re.compile('MSEXCH:')
4449+a1cre = re.compile('SMTP=(?P<addr>[^;]+); on ')
4450+a2cre = re.compile('(?P<addr>[^ ]+) on ')
4451+
4452+
4453+
4454
4455+def process(msg):
4456+ addrs = {}
4457+ it = email.Iterators.body_line_iterator(msg)
4458+ # Find the start line
4459+ for line in it:
4460+ if scre.search(line):
4461+ break
4462+ else:
4463+ return []
4464+ # Search each line until we hit the end line
4465+ for line in it:
4466+ if ecre.search(line):
4467+ break
4468+ mo = a1cre.search(line)
4469+ if not mo:
4470+ mo = a2cre.search(line)
4471+ if mo:
4472+ addrs[mo.group('addr')] = 1
4473+ return addrs.keys()
4474
4475=== added file 'Mailman/Bouncers/Exim.py'
4476--- Mailman/Bouncers/Exim.py 1970-01-01 00:00:00 +0000
4477+++ Mailman/Bouncers/Exim.py 2025-02-01 12:21:11 +0000
4478@@ -0,0 +1,30 @@
4479+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4480+#
4481+# This program is free software; you can redistribute it and/or
4482+# modify it under the terms of the GNU General Public License
4483+# as published by the Free Software Foundation; either version 2
4484+# of the License, or (at your option) any later version.
4485+#
4486+# This program is distributed in the hope that it will be useful,
4487+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4488+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4489+# GNU General Public License for more details.
4490+#
4491+# You should have received a copy of the GNU General Public License
4492+# along with this program; if not, write to the Free Software
4493+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4494+
4495+"""Parse bounce messages generated by Exim.
4496+
4497+Exim adds an X-Failed-Recipients: header to bounce messages containing
4498+an `addresslist' of failed addresses.
4499+
4500+"""
4501+
4502+from email.Utils import getaddresses
4503+
4504+
4505+
4506
4507+def process(msg):
4508+ all = msg.get_all('x-failed-recipients', [])
4509+ return [a for n, a in getaddresses(all)]
4510
4511=== added file 'Mailman/Bouncers/GroupWise.py'
4512--- Mailman/Bouncers/GroupWise.py 1970-01-01 00:00:00 +0000
4513+++ Mailman/Bouncers/GroupWise.py 2025-02-01 12:21:11 +0000
4514@@ -0,0 +1,72 @@
4515+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4516+#
4517+# This program is free software; you can redistribute it and/or
4518+# modify it under the terms of the GNU General Public License
4519+# as published by the Free Software Foundation; either version 2
4520+# of the License, or (at your option) any later version.
4521+#
4522+# This program is distributed in the hope that it will be useful,
4523+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4524+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4525+# GNU General Public License for more details.
4526+#
4527+# You should have received a copy of the GNU General Public License
4528+# along with this program; if not, write to the Free Software
4529+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4530+
4531+"""This appears to be the format for Novell GroupWise and NTMail
4532+
4533+X-Mailer: Novell GroupWise Internet Agent 5.5.3.1
4534+X-Mailer: NTMail v4.30.0012
4535+X-Mailer: Internet Mail Service (5.5.2653.19)
4536+"""
4537+
4538+import re
4539+from email.Message import Message
4540+from cStringIO import StringIO
4541+
4542+acre = re.compile(r'<(?P<addr>[^>]*)>')
4543+
4544+
4545+
4546
4547+def find_textplain(msg):
4548+ if msg.get_content_type() == 'text/plain':
4549+ return msg
4550+ if msg.is_multipart:
4551+ for part in msg.get_payload():
4552+ if not isinstance(part, Message):
4553+ continue
4554+ ret = find_textplain(part)
4555+ if ret:
4556+ return ret
4557+ return None
4558+
4559+
4560+
4561
4562+def process(msg):
4563+ if msg.get_content_type() <> 'multipart/mixed' or not msg['x-mailer']:
4564+ return None
4565+ if msg['x-mailer'][:3].lower() not in ('nov', 'ntm', 'int'):
4566+ return None
4567+ addrs = {}
4568+ # find the first text/plain part in the message
4569+ textplain = find_textplain(msg)
4570+ if not textplain:
4571+ return None
4572+ body = StringIO(textplain.get_payload())
4573+ while 1:
4574+ line = body.readline()
4575+ if not line:
4576+ break
4577+ mo = acre.search(line)
4578+ if mo:
4579+ addrs[mo.group('addr')] = 1
4580+ elif '@' in line:
4581+ i = line.find(' ')
4582+ if i == 0:
4583+ continue
4584+ if i < 0:
4585+ addrs[line] = 1
4586+ else:
4587+ addrs[line[:i]] = 1
4588+ return addrs.keys()
4589
4590=== added file 'Mailman/Bouncers/LLNL.py'
4591--- Mailman/Bouncers/LLNL.py 1970-01-01 00:00:00 +0000
4592+++ Mailman/Bouncers/LLNL.py 2025-02-01 12:21:11 +0000
4593@@ -0,0 +1,31 @@
4594+# Copyright (C) 2001-2018 by the Free Software Foundation, Inc.
4595+#
4596+# This program is free software; you can redistribute it and/or
4597+# modify it under the terms of the GNU General Public License
4598+# as published by the Free Software Foundation; either version 2
4599+# of the License, or (at your option) any later version.
4600+#
4601+# This program is distributed in the hope that it will be useful,
4602+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4603+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4604+# GNU General Public License for more details.
4605+#
4606+# You should have received a copy of the GNU General Public License
4607+# along with this program; if not, write to the Free Software
4608+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4609+
4610+"""LLNL's custom Sendmail bounce message."""
4611+
4612+import re
4613+import email
4614+
4615+acre = re.compile(r',\s*(?P<addr>\S+@[^,]+),', re.IGNORECASE)
4616+
4617+
4618+
4619
4620+def process(msg):
4621+ for line in email.Iterators.body_line_iterator(msg):
4622+ mo = acre.search(line)
4623+ if mo:
4624+ return [mo.group('addr')]
4625+ return []
4626
4627=== added file 'Mailman/Bouncers/Makefile.in'
4628--- Mailman/Bouncers/Makefile.in 1970-01-01 00:00:00 +0000
4629+++ Mailman/Bouncers/Makefile.in 2025-02-01 12:21:11 +0000
4630@@ -0,0 +1,75 @@
4631+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4632+#
4633+# This program is free software; you can redistribute it and/or
4634+# modify it under the terms of the GNU General Public License
4635+# as published by the Free Software Foundation; either version 2
4636+# of the License, or (at your option) any later version.
4637+#
4638+# This program is distributed in the hope that it will be useful,
4639+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4640+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4641+# GNU General Public License for more details.
4642+#
4643+# You should have received a copy of the GNU General Public License
4644+# along with this program; if not, write to the Free Software
4645+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4646+
4647+# NOTE: Makefile.in is converted into Makefile by the configure script
4648+# in the parent directory. Once configure has run, you can recreate
4649+# the Makefile by running just config.status.
4650+
4651+# Variables set by configure
4652+
4653+VPATH= @srcdir@
4654+srcdir= @srcdir@
4655+bindir= @bindir@
4656+prefix= @prefix@
4657+exec_prefix= @exec_prefix@
4658+DESTDIR=
4659+
4660+CC= @CC@
4661+CHMOD= @CHMOD@
4662+INSTALL= @INSTALL@
4663+
4664+DEFS= @DEFS@
4665+
4666+# Customizable but not set by configure
4667+
4668+OPT= @OPT@
4669+CFLAGS= $(OPT) $(DEFS)
4670+PACKAGEDIR= $(prefix)/Mailman/Bouncers
4671+SHELL= /bin/sh
4672+
4673+MODULES= *.py
4674+
4675+# Modes for directories and executables created by the install
4676+# process. Default to group-writable directories but
4677+# user-only-writable for executables.
4678+DIRMODE= 775
4679+EXEMODE= 755
4680+FILEMODE= 644
4681+INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
4682+
4683+
4684+# Rules
4685+
4686+all:
4687+
4688+install:
4689+ for f in $(MODULES); \
4690+ do \
4691+ $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
4692+ done
4693+
4694+finish:
4695+
4696+clean:
4697+
4698+distclean:
4699+ -rm *.pyc
4700+ -rm Makefile
4701+
4702+
4703+# Local Variables:
4704+# indent-tabs-mode: t
4705+# End:
4706
4707=== added file 'Mailman/Bouncers/Microsoft.py'
4708--- Mailman/Bouncers/Microsoft.py 1970-01-01 00:00:00 +0000
4709+++ Mailman/Bouncers/Microsoft.py 2025-02-01 12:21:11 +0000
4710@@ -0,0 +1,53 @@
4711+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4712+#
4713+# This program is free software; you can redistribute it and/or
4714+# modify it under the terms of the GNU General Public License
4715+# as published by the Free Software Foundation; either version 2
4716+# of the License, or (at your option) any later version.
4717+#
4718+# This program is distributed in the hope that it will be useful,
4719+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4720+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4721+# GNU General Public License for more details.
4722+#
4723+# You should have received a copy of the GNU General Public License
4724+# along with this program; if not, write to the Free Software
4725+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4726+
4727+"""Microsoft's `SMTPSVC' nears I kin tell."""
4728+
4729+import re
4730+from cStringIO import StringIO
4731+from types import ListType
4732+
4733+scre = re.compile(r'transcript of session follows', re.IGNORECASE)
4734+
4735+
4736+
4737
4738+def process(msg):
4739+ if msg.get_content_type() <> 'multipart/mixed':
4740+ return None
4741+ # Find the first subpart, which has no MIME type
4742+ try:
4743+ subpart = msg.get_payload(0)
4744+ except IndexError:
4745+ # The message *looked* like a multipart but wasn't
4746+ return None
4747+ data = subpart.get_payload()
4748+ if isinstance(data, ListType):
4749+ # The message is a multi-multipart, so not a matching bounce
4750+ return None
4751+ body = StringIO(data)
4752+ state = 0
4753+ addrs = []
4754+ while 1:
4755+ line = body.readline()
4756+ if not line:
4757+ break
4758+ if state == 0:
4759+ if scre.search(line):
4760+ state = 1
4761+ if state == 1:
4762+ if '@' in line:
4763+ addrs.append(line)
4764+ return addrs
4765
4766=== added file 'Mailman/Bouncers/Netscape.py'
4767--- Mailman/Bouncers/Netscape.py 1970-01-01 00:00:00 +0000
4768+++ Mailman/Bouncers/Netscape.py 2025-02-01 12:21:11 +0000
4769@@ -0,0 +1,88 @@
4770+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4771+#
4772+# This program is free software; you can redistribute it and/or
4773+# modify it under the terms of the GNU General Public License
4774+# as published by the Free Software Foundation; either version 2
4775+# of the License, or (at your option) any later version.
4776+#
4777+# This program is distributed in the hope that it will be useful,
4778+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4779+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4780+# GNU General Public License for more details.
4781+#
4782+# You should have received a copy of the GNU General Public License
4783+# along with this program; if not, write to the Free Software
4784+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4785+
4786+"""Netscape Messaging Server bounce formats.
4787+
4788+I've seen at least one NMS server version 3.6 (envy.gmp.usyd.edu.au) bounce
4789+messages of this format. Bounces come in DSN MIME format, but don't include
4790+any -Recipient: headers. Gotta just parse the text :(
4791+
4792+NMS 4.1 (dfw-smtpin1.email.verio.net) seems even worse, but we'll try to
4793+decipher the format here too.
4794+
4795+"""
4796+
4797+import re
4798+from cStringIO import StringIO
4799+
4800+pcre = re.compile(
4801+ r'This Message was undeliverable due to the following reason:',
4802+ re.IGNORECASE)
4803+
4804+acre = re.compile(
4805+ r'(?P<reply>please reply to)?.*<(?P<addr>[^>]*)>',
4806+ re.IGNORECASE)
4807+
4808+
4809+
4810
4811+def flatten(msg, leaves):
4812+ # give us all the leaf (non-multipart) subparts
4813+ if msg.is_multipart():
4814+ for part in msg.get_payload():
4815+ flatten(part, leaves)
4816+ else:
4817+ leaves.append(msg)
4818+
4819+
4820+
4821
4822+def process(msg):
4823+ # Sigh. Some show NMS 3.6's show
4824+ # multipart/report; report-type=delivery-status
4825+ # and some show
4826+ # multipart/mixed;
4827+ if not msg.is_multipart():
4828+ return None
4829+ # We're looking for a text/plain subpart occuring before a
4830+ # message/delivery-status subpart.
4831+ plainmsg = None
4832+ leaves = []
4833+ flatten(msg, leaves)
4834+ for i, subpart in zip(range(len(leaves)-1), leaves):
4835+ if subpart.get_content_type() == 'text/plain':
4836+ plainmsg = subpart
4837+ break
4838+ if not plainmsg:
4839+ return None
4840+ # Total guesswork, based on captured examples...
4841+ body = StringIO(plainmsg.get_payload())
4842+ addrs = []
4843+ while 1:
4844+ line = body.readline()
4845+ if not line:
4846+ break
4847+ mo = pcre.search(line)
4848+ if mo:
4849+ # We found a bounce section, but I have no idea what the official
4850+ # format inside here is. :( We'll just search for <addr>
4851+ # strings.
4852+ while 1:
4853+ line = body.readline()
4854+ if not line:
4855+ break
4856+ mo = acre.search(line)
4857+ if mo and not mo.group('reply'):
4858+ addrs.append(mo.group('addr'))
4859+ return addrs
4860
4861=== added file 'Mailman/Bouncers/Postfix.py'
4862--- Mailman/Bouncers/Postfix.py 1970-01-01 00:00:00 +0000
4863+++ Mailman/Bouncers/Postfix.py 2025-02-01 12:21:11 +0000
4864@@ -0,0 +1,85 @@
4865+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4866+#
4867+# This program is free software; you can redistribute it and/or
4868+# modify it under the terms of the GNU General Public License
4869+# as published by the Free Software Foundation; either version 2
4870+# of the License, or (at your option) any later version.
4871+#
4872+# This program is distributed in the hope that it will be useful,
4873+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4874+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4875+# GNU General Public License for more details.
4876+#
4877+# You should have received a copy of the GNU General Public License
4878+# along with this program; if not, write to the Free Software
4879+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
4880+
4881+"""Parse bounce messages generated by Postfix.
4882+
4883+This also matches something called `Keftamail' which looks just like Postfix
4884+bounces with the word Postfix scratched out and the word `Keftamail' written
4885+in in crayon.
4886+
4887+It also matches something claiming to be `The BNS Postfix program', and
4888+`SMTP_Gateway'. Everybody's gotta be different, huh?
4889+"""
4890+
4891+import re
4892+from cStringIO import StringIO
4893+
4894+
4895+
4896
4897+def flatten(msg, leaves):
4898+ # give us all the leaf (non-multipart) subparts
4899+ if msg.is_multipart():
4900+ for part in msg.get_payload():
4901+ flatten(part, leaves)
4902+ else:
4903+ leaves.append(msg)
4904+
4905+
4906+
4907
4908+# are these heuristics correct or guaranteed?
4909+pcre = re.compile(r'[ \t]*the\s*(bns)?\s*(postfix|keftamail|smtp_gateway)',
4910+ re.IGNORECASE)
4911+rcre = re.compile(r'failure reason:$', re.IGNORECASE)
4912+acre = re.compile(r'<(?P<addr>[^>]*)>:')
4913+
4914+def findaddr(msg):
4915+ addrs = []
4916+ body = StringIO(msg.get_payload())
4917+ # simple state machine
4918+ # 0 == nothing found
4919+ # 1 == salutation found
4920+ state = 0
4921+ while 1:
4922+ line = body.readline()
4923+ if not line:
4924+ break
4925+ # preserve leading whitespace
4926+ line = line.rstrip()
4927+ # yes use match to match at beginning of string
4928+ if state == 0 and (pcre.match(line) or rcre.match(line)):
4929+ state = 1
4930+ elif state == 1 and line:
4931+ mo = acre.search(line)
4932+ if mo:
4933+ addrs.append(mo.group('addr'))
4934+ # probably a continuation line
4935+ return addrs
4936+
4937+
4938+
4939
4940+def process(msg):
4941+ if msg.get_content_type() not in ('multipart/mixed', 'multipart/report'):
4942+ return None
4943+ # We're looking for the plain/text subpart with a Content-Description: of
4944+ # `notification'.
4945+ leaves = []
4946+ flatten(msg, leaves)
4947+ for subpart in leaves:
4948+ if subpart.get_content_type() == 'text/plain' and \
4949+ subpart.get('content-description', '').lower() == 'notification':
4950+ # then...
4951+ return findaddr(subpart)
4952+ return None
4953
4954=== added file 'Mailman/Bouncers/Qmail.py'
4955--- Mailman/Bouncers/Qmail.py 1970-01-01 00:00:00 +0000
4956+++ Mailman/Bouncers/Qmail.py 2025-02-01 12:21:11 +0000
4957@@ -0,0 +1,74 @@
4958+# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
4959+#
4960+# This program is free software; you can redistribute it and/or
4961+# modify it under the terms of the GNU General Public License
4962+# as published by the Free Software Foundation; either version 2
4963+# of the License, or (at your option) any later version.
4964+#
4965+# This program is distributed in the hope that it will be useful,
4966+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4967+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4968+# GNU General Public License for more details.
4969+#
4970+# You should have received a copy of the GNU General Public License
4971+# along with this program; if not, write to the Free Software
4972+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
4973+# USA.
4974+
4975+"""Parse bounce messages generated by qmail.
4976+
4977+Qmail actually has a standard, called QSBMF (qmail-send bounce message
4978+format), as described in
4979+
4980+ http://cr.yp.to/proto/qsbmf.txt
4981+
4982+This module should be conformant.
4983+
4984+"""
4985+
4986+import re
4987+import email.Iterators
4988+
4989+# Other (non-standard?) intros have been observed in the wild.
4990+introtags = [
4991+ 'Hi. This is the',
4992+ 'Hi. The MTA program at',
4993+ "We're sorry. There's a problem",
4994+ 'Check your send e-mail address.',
4995+ 'This is the mail delivery agent at',
4996+ 'Unfortunately, your mail was not delivered',
4997+ 'Your mail message to the following',
4998+ ]
4999+acre = re.compile(r'<(?P<addr>[^>]*)>:')
5000+
The diff has been truncated for viewing.