Merge lp:~mmlmtp/mailman/mm3lmtp into lp:mailman

Proposed by Ian Eiloart
Status: Rejected
Rejected by: Barry Warsaw
Proposed branch: lp:~mmlmtp/mailman/mm3lmtp
Merge into: lp:mailman
Diff against target: 568979 lines (+565598/-0) (has conflicts)
627 files modified
PKG-INFO (+14/-0)
README.txt (+197/-0)
TODO.txt (+15/-0)
bin/clone_member (+219/-0)
bin/discard (+120/-0)
bin/fix_url.py (+93/-0)
bin/list_admins (+101/-0)
bin/msgfmt.py (+203/-0)
bin/po2templ.py (+90/-0)
bin/pygettext.py (+545/-0)
bin/remove_members (+186/-0)
bin/reset_pw.py (+83/-0)
bin/sync_members (+286/-0)
bin/templ2pot.py (+120/-0)
bin/transcheck (+412/-0)
contrib/README (+4/-0)
contrib/README.check_perms_grsecurity (+14/-0)
contrib/README.mm-handler (+215/-0)
contrib/README.mmdsr (+45/-0)
contrib/auto (+116/-0)
contrib/check_perms_grsecurity.py (+182/-0)
contrib/mailman.mc (+143/-0)
contrib/majordomo2mailman.pl (+691/-0)
contrib/mm-handler (+236/-0)
contrib/mmdsr (+572/-0)
contrib/qmail-to-mailman.py (+116/-0)
contrib/rotatelogs.py (+104/-0)
contrib/virtusertable (+37/-0)
cron/crontab.in.in (+21/-0)
data/coverage.py (+952/-0)
data/mailman.in (+54/-0)
data/paths.py.in (+90/-0)
docs/ACKNOWLEDGMENTS.txt (+251/-0)
docs/ALPHA.txt (+59/-0)
docs/NEWS.txt (+181/-0)
docs/OLD-NEWS.txt (+2835/-0)
docs/STYLEGUIDE.txt (+162/-0)
docs/gnu-COPYING-GPL (+340/-0)
docs/man/add_members.1 (+60/-0)
docs/man/check_db.1 (+60/-0)
docs/man/check_perms.1 (+46/-0)
docs/man/clone_member.1 (+71/-0)
docs/man/find_member.1 (+64/-0)
docs/man/list_members.1 (+78/-0)
docs/man/remove_members.1 (+63/-0)
docs/man/sync_members.1 (+81/-0)
docs/man/transcheck.1 (+41/-0)
docs/posting-flow-chart.ps (+735/-0)
ez_setup.py (+228/-0)
foo.members (+2/-0)
mailman.egg-info/PKG-INFO (+14/-0)
mailman.egg-info/SOURCES.txt (+598/-0)
mailman.egg-info/dependency_links.txt (+1/-0)
mailman.egg-info/entry_points.txt (+63/-0)
mailman.egg-info/requires.txt (+5/-0)
mailman.egg-info/top_level.txt (+1/-0)
mailman/Archiver/Archiver.py (+231/-0)
mailman/Archiver/HyperArch.py (+1245/-0)
mailman/Archiver/HyperDatabase.py (+339/-0)
mailman/Archiver/__init__.py (+18/-0)
mailman/Archiver/pipermail.py (+874/-0)
mailman/Bouncer.py (+250/-0)
mailman/Bouncers/BouncerAPI.py (+64/-0)
mailman/Bouncers/Caiwireless.py (+46/-0)
mailman/Bouncers/Compuserve.py (+46/-0)
mailman/Bouncers/DSN.py (+100/-0)
mailman/Bouncers/Exchange.py (+47/-0)
mailman/Bouncers/Exim.py (+31/-0)
mailman/Bouncers/GroupWise.py (+71/-0)
mailman/Bouncers/LLNL.py (+32/-0)
mailman/Bouncers/Microsoft.py (+53/-0)
mailman/Bouncers/Netscape.py (+89/-0)
mailman/Bouncers/Postfix.py (+85/-0)
mailman/Bouncers/Qmail.py (+72/-0)
mailman/Bouncers/SMTP32.py (+60/-0)
mailman/Bouncers/SimpleMatch.py (+204/-0)
mailman/Bouncers/SimpleWarning.py (+62/-0)
mailman/Bouncers/Sina.py (+47/-0)
mailman/Bouncers/Yahoo.py (+54/-0)
mailman/Bouncers/Yale.py (+80/-0)
mailman/Cgi/Auth.py (+60/-0)
mailman/Cgi/admin.py (+1433/-0)
mailman/Cgi/admindb.py (+813/-0)
mailman/Cgi/confirm.py (+834/-0)
mailman/Cgi/create.py (+396/-0)
mailman/Cgi/edithtml.py (+175/-0)
mailman/Cgi/listinfo.py (+207/-0)
mailman/Cgi/options.py (+1000/-0)
mailman/Cgi/private.py (+190/-0)
mailman/Cgi/rmlist.py (+243/-0)
mailman/Cgi/roster.py (+129/-0)
mailman/Cgi/subscribe.py (+252/-0)
mailman/Cgi/wsgi_app.py (+286/-0)
mailman/Commands/cmd_confirm.py (+98/-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 (+65/-0)
mailman/Commands/cmd_password.py (+122/-0)
mailman/Commands/cmd_remove.py (+20/-0)
mailman/Commands/cmd_set.py (+360/-0)
mailman/Commands/cmd_stop.py (+20/-0)
mailman/Commands/cmd_subscribe.py (+133/-0)
mailman/Commands/cmd_unsubscribe.py (+87/-0)
mailman/Commands/cmd_who.py (+152/-0)
mailman/Defaults.py (+1347/-0)
mailman/Deliverer.py (+174/-0)
mailman/Digester.py (+57/-0)
mailman/Errors.py (+195/-0)
mailman/Gui/Archive.py (+45/-0)
mailman/Gui/Autoresponse.py (+99/-0)
mailman/Gui/Bounce.py (+195/-0)
mailman/Gui/ContentFilter.py (+199/-0)
mailman/Gui/Digest.py (+161/-0)
mailman/Gui/GUIBase.py (+209/-0)
mailman/Gui/General.py (+464/-0)
mailman/Gui/Language.py (+128/-0)
mailman/Gui/Membership.py (+34/-0)
mailman/Gui/NonDigest.py (+158/-0)
mailman/Gui/Passwords.py (+31/-0)
mailman/Gui/Privacy.py (+537/-0)
mailman/Gui/Topics.py (+162/-0)
mailman/Gui/Usenet.py (+140/-0)
mailman/Gui/__init__.py (+33/-0)
mailman/HTMLFormatter.py (+437/-0)
mailman/MTA/Manual.py (+139/-0)
mailman/MTA/Postfix.py (+413/-0)
mailman/MTA/Utils.py (+87/-0)
mailman/MailList.py (+731/-0)
mailman/Mailbox.py (+105/-0)
mailman/Message.py (+309/-0)
mailman/SafeDict.py (+55/-0)
mailman/SecurityManager.py (+306/-0)
mailman/UserDesc.py (+65/-0)
mailman/Utils.py (+918/-0)
mailman/app/archiving.py (+110/-0)
mailman/app/bounces.py (+62/-0)
mailman/app/chains.py (+115/-0)
mailman/app/lifecycle.py (+117/-0)
mailman/app/membership.py (+199/-0)
mailman/app/moderator.py (+340/-0)
mailman/app/pipelines.py (+122/-0)
mailman/app/plugins.py (+65/-0)
mailman/app/registrar.py (+144/-0)
mailman/app/replybot.py (+123/-0)
mailman/app/rules.py (+42/-0)
mailman/app/styles.py (+294/-0)
mailman/bin/__init__.py (+62/-0)
mailman/bin/add_members.py (+187/-0)
mailman/bin/arch.py (+152/-0)
mailman/bin/bounces.py (+61/-0)
mailman/bin/bumpdigests.py (+74/-0)
mailman/bin/change_pw.py (+177/-0)
mailman/bin/check_perms.py (+407/-0)
mailman/bin/checkdbs.py (+199/-0)
mailman/bin/cleanarch.py (+133/-0)
mailman/bin/config_list.py (+332/-0)
mailman/bin/confirm.py (+63/-0)
mailman/bin/create_list.py (+134/-0)
mailman/bin/disabled.py (+201/-0)
mailman/bin/docs/master.txt (+44/-0)
mailman/bin/dumpdb.py (+90/-0)
mailman/bin/export.py (+314/-0)
mailman/bin/find_member.py (+135/-0)
mailman/bin/gate_news.py (+245/-0)
mailman/bin/genaliases.py (+85/-0)
mailman/bin/import.py (+315/-0)
mailman/bin/inject.py (+91/-0)
mailman/bin/join.py (+63/-0)
mailman/bin/leave.py (+62/-0)
mailman/bin/list_lists.py (+105/-0)
mailman/bin/list_members.py (+201/-0)
mailman/bin/list_owners.py (+88/-0)
mailman/bin/mailmanctl.py (+234/-0)
mailman/bin/make_instance.py (+173/-0)
mailman/bin/master.py (+432/-0)
mailman/bin/mmsitepass.py (+113/-0)
mailman/bin/nightly_gzip.py (+117/-0)
mailman/bin/owner.py (+68/-0)
mailman/bin/post.py (+71/-0)
mailman/bin/qrunner.py (+271/-0)
mailman/bin/remove_list.py (+87/-0)
mailman/bin/request.py (+65/-0)
mailman/bin/senddigests.py (+83/-0)
mailman/bin/set_members.py (+185/-0)
mailman/bin/show_config.py (+97/-0)
mailman/bin/show_qfiles.py (+65/-0)
mailman/bin/testall.py (+283/-0)
mailman/bin/unshunt.py (+75/-0)
mailman/bin/update.py (+659/-0)
mailman/bin/version.py (+46/-0)
mailman/bin/withlist.py (+218/-0)
mailman/chains/accept.py (+55/-0)
mailman/chains/base.py (+119/-0)
mailman/chains/builtin.py (+84/-0)
mailman/chains/discard.py (+43/-0)
mailman/chains/headers.py (+151/-0)
mailman/chains/hold.py (+176/-0)
mailman/chains/reject.py (+55/-0)
mailman/configuration.py (+261/-0)
mailman/constants.py (+39/-0)
mailman/database/__init__.py (+153/-0)
mailman/database/address.py (+91/-0)
mailman/database/language.py (+30/-0)
mailman/database/listmanager.py (+76/-0)
mailman/database/mailinglist.py (+259/-0)
mailman/database/mailman.sql (+209/-0)
mailman/database/member.py (+97/-0)
mailman/database/message.py (+42/-0)
mailman/database/messagestore.py (+132/-0)
mailman/database/model.py (+54/-0)
mailman/database/pending.py (+167/-0)
mailman/database/preferences.py (+40/-0)
mailman/database/requests.py (+134/-0)
mailman/database/roster.py (+266/-0)
mailman/database/transaction.py (+51/-0)
mailman/database/types.py (+57/-0)
mailman/database/user.py (+85/-0)
mailman/database/usermanager.py (+99/-0)
mailman/database/version.py (+30/-0)
mailman/docs/addresses.txt (+232/-0)
mailman/docs/bounces.txt (+109/-0)
mailman/docs/chains.txt (+341/-0)
mailman/docs/languages.txt (+104/-0)
mailman/docs/lifecycle.txt (+138/-0)
mailman/docs/listmanager.txt (+89/-0)
mailman/docs/membership.txt (+231/-0)
mailman/docs/message.txt (+50/-0)
mailman/docs/messagestore.txt (+114/-0)
mailman/docs/mlist-addresses.txt (+75/-0)
mailman/docs/pending.txt (+95/-0)
mailman/docs/pipelines.txt (+174/-0)
mailman/docs/registration.txt (+364/-0)
mailman/docs/requests.txt (+856/-0)
mailman/docs/styles.txt (+162/-0)
mailman/docs/usermanager.txt (+125/-0)
mailman/docs/users.txt (+196/-0)
mailman/extras/mailman.cfg.in (+46/-0)
mailman/htmlformat.py (+670/-0)
mailman/i18n.py (+185/-0)
mailman/initialize.py (+78/-0)
mailman/inject.py (+78/-0)
mailman/interact.py (+68/-0)
mailman/interfaces/__init__.py (+78/-0)
mailman/interfaces/address.py (+90/-0)
mailman/interfaces/archiver.py (+58/-0)
mailman/interfaces/chain.py (+98/-0)
mailman/interfaces/database.py (+87/-0)
mailman/interfaces/domain.py (+53/-0)
mailman/interfaces/errors.py (+28/-0)
mailman/interfaces/handler.py (+37/-0)
mailman/interfaces/languages.py (+80/-0)
mailman/interfaces/listmanager.py (+82/-0)
mailman/interfaces/mailinglist.py (+269/-0)
mailman/interfaces/member.py (+185/-0)
mailman/interfaces/messages.py (+102/-0)
mailman/interfaces/mlistrequest.py (+29/-0)
mailman/interfaces/pending.py (+88/-0)
mailman/interfaces/permissions.py (+28/-0)
mailman/interfaces/pipeline.py (+32/-0)
mailman/interfaces/preferences.py (+69/-0)
mailman/interfaces/registrar.py (+75/-0)
mailman/interfaces/requests.py (+105/-0)
mailman/interfaces/roster.py (+53/-0)
mailman/interfaces/rules.py (+45/-0)
mailman/interfaces/runner.py (+31/-0)
mailman/interfaces/styles.py (+111/-0)
mailman/interfaces/switchboard.py (+82/-0)
mailman/interfaces/user.py (+76/-0)
mailman/interfaces/usermanager.py (+85/-0)
mailman/languages.py (+60/-0)
mailman/loginit.py (+182/-0)
mailman/messages/ar/LC_MESSAGES/mailman.po (+15702/-0)
mailman/messages/ca/LC_MESSAGES/mailman.po (+15421/-0)
mailman/messages/cs/LC_MESSAGES/mailman.po (+13393/-0)
mailman/messages/da/LC_MESSAGES/mailman.po (+15972/-0)
mailman/messages/de/LC_MESSAGES/mailman.po (+15318/-0)
mailman/messages/de/README.de (+21/-0)
mailman/messages/docstring.files (+59/-0)
mailman/messages/es/LC_MESSAGES/mailman.po (+16349/-0)
mailman/messages/es/README.es (+82/-0)
mailman/messages/et/LC_MESSAGES/mailman.po (+14338/-0)
mailman/messages/eu/LC_MESSAGES/mailman.po (+14145/-0)
mailman/messages/eu/README.eu (+103/-0)
mailman/messages/fi/LC_MESSAGES/mailman.po (+13862/-0)
mailman/messages/fi/README.fi (+13/-0)
mailman/messages/fr/LC_MESSAGES/mailman.po (+15420/-0)
mailman/messages/fr/README.fr (+7/-0)
mailman/messages/hr/LC_MESSAGES/mailman.po (+13737/-0)
mailman/messages/hu/FAQ.hu (+464/-0)
mailman/messages/hu/INSTALL.hu (+640/-0)
mailman/messages/hu/LC_MESSAGES/mailman.po (+14882/-0)
mailman/messages/hu/README.BSD.hu (+28/-0)
mailman/messages/hu/README.CONTRIB.hu (+17/-0)
mailman/messages/hu/README.EXIM.hu (+359/-0)
mailman/messages/hu/README.LINUX.hu (+59/-0)
mailman/messages/hu/README.MACOSX.hu (+31/-0)
mailman/messages/hu/README.NETSCAPE.hu (+57/-0)
mailman/messages/hu/README.POSTFIX.hu (+239/-0)
mailman/messages/hu/README.QMAIL.hu (+186/-0)
mailman/messages/hu/README.SENDMAIL.hu (+86/-0)
mailman/messages/hu/README.USERAGENT.hu (+49/-0)
mailman/messages/hu/README.hu (+271/-0)
mailman/messages/hu/UPGRADING.hu (+391/-0)
mailman/messages/ia/LC_MESSAGES/mailman.po (+14426/-0)
mailman/messages/it/LC_MESSAGES/mailman.po (+15623/-0)
mailman/messages/it/README.it (+32/-0)
mailman/messages/ja/INSTALL (+615/-0)
mailman/messages/ja/LC_MESSAGES/mailman.po (+14328/-0)
mailman/messages/ja/README (+214/-0)
mailman/messages/ja/README.ja (+109/-0)
mailman/messages/ja/UPGRADING (+215/-0)
mailman/messages/ja/doc/Defaults.py.in (+1442/-0)
mailman/messages/ja/doc/mailman-install.tex (+1933/-0)
mailman/messages/ja/doc/mailman-member.tex (+1787/-0)
mailman/messages/ko/LC_MESSAGES/mailman.po (+12970/-0)
mailman/messages/ko/README.ko (+26/-0)
mailman/messages/lt/LC_MESSAGES/mailman.po (+12116/-0)
mailman/messages/mailman.pot (+10031/-0)
mailman/messages/marked.files (+130/-0)
mailman/messages/nl/LC_MESSAGES/mailman.po (+13629/-0)
mailman/messages/no/LC_MESSAGES/mailman.po (+15503/-0)
mailman/messages/pl/LC_MESSAGES/mailman.po (+13120/-0)
mailman/messages/pl/README.pl (+28/-0)
mailman/messages/pt/LC_MESSAGES/mailman.po (+14924/-0)
mailman/messages/pt_BR/LC_MESSAGES/mailman.po (+15033/-0)
mailman/messages/ro/LC_MESSAGES/mailman.po (+14633/-0)
mailman/messages/ru/LC_MESSAGES/mailman.po (+14862/-0)
mailman/messages/ru/README.ru (+17/-0)
mailman/messages/sl/LC_MESSAGES/mailman.po (+17650/-0)
mailman/messages/sr/LC_MESSAGES/mailman.po (+11873/-0)
mailman/messages/sr/readme.sr (+6/-0)
mailman/messages/sv/LC_MESSAGES/mailman.po (+18097/-0)
mailman/messages/sv/README.sv (+30/-0)
mailman/messages/tr/LC_MESSAGES/mailman.po (+13641/-0)
mailman/messages/uk/LC_MESSAGES/mailman.po (+14897/-0)
mailman/messages/vi/LC_MESSAGES/mailman.po (+14584/-0)
mailman/messages/zh_CN/LC_MESSAGES/mailman.po (+14142/-0)
mailman/messages/zh_TW/LC_MESSAGES/mailman.po (+12964/-0)
mailman/options.py (+133/-0)
mailman/passwords.py (+249/-0)
mailman/pipeline/__init__.py (+50/-0)
mailman/pipeline/acknowledge.py (+78/-0)
mailman/pipeline/after_delivery.py (+44/-0)
mailman/pipeline/avoid_duplicates.py (+113/-0)
mailman/pipeline/calculate_recipients.py (+144/-0)
mailman/pipeline/cleanse.py (+71/-0)
mailman/pipeline/cleanse_dkim.py (+53/-0)
mailman/pipeline/cook_headers.py (+358/-0)
mailman/pipeline/decorate.py (+228/-0)
mailman/pipeline/docs/ack-headers.txt (+41/-0)
mailman/pipeline/docs/acknowledge.txt (+162/-0)
mailman/pipeline/docs/after-delivery.txt (+28/-0)
mailman/pipeline/docs/archives.txt (+133/-0)
mailman/pipeline/docs/avoid-duplicates.txt (+169/-0)
mailman/pipeline/docs/calc-recips.txt (+101/-0)
mailman/pipeline/docs/cleanse.txt (+95/-0)
mailman/pipeline/docs/cook-headers.txt (+328/-0)
mailman/pipeline/docs/decorate.txt (+318/-0)
mailman/pipeline/docs/digests.txt (+536/-0)
mailman/pipeline/docs/file-recips.txt (+97/-0)
mailman/pipeline/docs/filtering.txt (+341/-0)
mailman/pipeline/docs/nntp.txt (+68/-0)
mailman/pipeline/docs/reply-to.txt (+128/-0)
mailman/pipeline/docs/replybot.txt (+216/-0)
mailman/pipeline/docs/scrubber.txt (+214/-0)
mailman/pipeline/docs/subject-munging.txt (+245/-0)
mailman/pipeline/docs/tagger.txt (+237/-0)
mailman/pipeline/docs/to-outgoing.txt (+155/-0)
mailman/pipeline/file_recipients.py (+64/-0)
mailman/pipeline/mime_delete.py (+280/-0)
mailman/pipeline/moderate.py (+167/-0)
mailman/pipeline/owner_recipients.py (+27/-0)
mailman/pipeline/replybot.py (+130/-0)
mailman/pipeline/scrubber.py (+520/-0)
mailman/pipeline/smtp_direct.py (+433/-0)
mailman/pipeline/tagger.py (+181/-0)
mailman/pipeline/to_archive.py (+54/-0)
mailman/pipeline/to_digest.py (+439/-0)
mailman/pipeline/to_outgoing.py (+73/-0)
mailman/pipeline/to_usenet.py (+66/-0)
mailman/queue/__init__.py (+435/-0)
mailman/queue/archive.py (+85/-0)
mailman/queue/bounce.py (+316/-0)
mailman/queue/command.py (+230/-0)
mailman/queue/docs/OVERVIEW.txt (+78/-0)
mailman/queue/docs/archiver.txt (+37/-0)
mailman/queue/docs/incoming.txt (+203/-0)
mailman/queue/docs/lmtp.txt (+166/-0)
mailman/queue/docs/news.txt (+158/-0)
mailman/queue/docs/outgoing.txt (+96/-0)
mailman/queue/docs/runner.txt (+70/-0)
mailman/queue/docs/switchboard.txt (+149/-0)
mailman/queue/http.py (+73/-0)
mailman/queue/incoming.py (+44/-0)
mailman/queue/lmtp.py (+488/-0)
mailman/queue/maildir.py (+189/-0)
mailman/queue/news.py (+166/-0)
mailman/queue/outgoing.py (+130/-0)
mailman/queue/pipeline.py (+38/-0)
mailman/queue/retry.py (+40/-0)
mailman/queue/virgin.py (+42/-0)
mailman/rules/__init__.py (+50/-0)
mailman/rules/administrivia.py (+98/-0)
mailman/rules/any.py (+41/-0)
mailman/rules/approved.py (+117/-0)
mailman/rules/docs/administrivia.txt (+100/-0)
mailman/rules/docs/approve.txt (+473/-0)
mailman/rules/docs/emergency.txt (+75/-0)
mailman/rules/docs/header-matching.txt (+145/-0)
mailman/rules/docs/implicit-dest.txt (+76/-0)
mailman/rules/docs/loop.txt (+49/-0)
mailman/rules/docs/max-size.txt (+40/-0)
mailman/rules/docs/moderation.txt (+70/-0)
mailman/rules/docs/news-moderation.txt (+37/-0)
mailman/rules/docs/no-subject.txt (+34/-0)
mailman/rules/docs/recipients.txt (+41/-0)
mailman/rules/docs/rules.txt (+70/-0)
mailman/rules/docs/suspicious.txt (+36/-0)
mailman/rules/docs/truth.txt (+10/-0)
mailman/rules/emergency.py (+44/-0)
mailman/rules/implicit_dest.py (+95/-0)
mailman/rules/loop.py (+44/-0)
mailman/rules/max_recipients.py (+48/-0)
mailman/rules/max_size.py (+46/-0)
mailman/rules/moderation.py (+66/-0)
mailman/rules/news_moderation.py (+44/-0)
mailman/rules/no_subject.py (+42/-0)
mailman/rules/suspicious.py (+94/-0)
mailman/rules/truth.py (+41/-0)
mailman/templates/en/adminaddrchgack.txt (+4/-0)
mailman/templates/en/admindbdetails.html (+65/-0)
mailman/templates/en/admindbpreamble.html (+10/-0)
mailman/templates/en/admindbsummary.html (+14/-0)
mailman/templates/en/adminsubscribeack.txt (+1/-0)
mailman/templates/en/adminunsubscribeack.txt (+1/-0)
mailman/templates/en/admlogin.html (+39/-0)
mailman/templates/en/approve.txt (+15/-0)
mailman/templates/en/archidxentry.html (+4/-0)
mailman/templates/en/archidxfoot.html (+21/-0)
mailman/templates/en/archidxhead.html (+24/-0)
mailman/templates/en/archlistend.html (+1/-0)
mailman/templates/en/archliststart.html (+4/-0)
mailman/templates/en/archtoc.html (+20/-0)
mailman/templates/en/archtocentry.html (+12/-0)
mailman/templates/en/archtocnombox.html (+18/-0)
mailman/templates/en/article.html (+50/-0)
mailman/templates/en/bounce.txt (+13/-0)
mailman/templates/en/checkdbs.txt (+7/-0)
mailman/templates/en/convert.txt (+34/-0)
mailman/templates/en/cronpass.txt (+19/-0)
mailman/templates/en/disabled.txt (+25/-0)
mailman/templates/en/emptyarchive.html (+15/-0)
mailman/templates/en/headfoot.html (+28/-0)
mailman/templates/en/help.txt (+33/-0)
mailman/templates/en/invite.txt (+20/-0)
mailman/templates/en/listinfo.html (+143/-0)
mailman/templates/en/masthead.txt (+13/-0)
mailman/templates/en/newlist.txt (+35/-0)
mailman/templates/en/nomoretoday.txt (+8/-0)
mailman/templates/en/options.html (+316/-0)
mailman/templates/en/postack.txt (+8/-0)
mailman/templates/en/postauth.txt (+13/-0)
mailman/templates/en/postheld.txt (+15/-0)
mailman/templates/en/private.html (+43/-0)
mailman/templates/en/probe.txt (+25/-0)
mailman/templates/en/refuse.txt (+13/-0)
mailman/templates/en/roster.html (+52/-0)
mailman/templates/en/subauth.txt (+11/-0)
mailman/templates/en/subscribe.html (+8/-0)
mailman/templates/en/subscribeack.txt (+25/-0)
mailman/templates/en/unsub.txt (+23/-0)
mailman/templates/en/unsubauth.txt (+11/-0)
mailman/templates/en/userpass.txt (+24/-0)
mailman/templates/en/verify.txt (+19/-0)
mailman/tests/bounces/bounce_01.txt (+95/-0)
mailman/tests/bounces/bounce_02.txt (+36/-0)
mailman/tests/bounces/bounce_03.txt (+109/-0)
mailman/tests/bounces/dsn_01.txt (+217/-0)
mailman/tests/bounces/dsn_02.txt (+187/-0)
mailman/tests/bounces/dsn_03.txt (+144/-0)
mailman/tests/bounces/dsn_04.txt (+202/-0)
mailman/tests/bounces/dsn_05.txt (+125/-0)
mailman/tests/bounces/dsn_06.txt (+122/-0)
mailman/tests/bounces/dsn_07.txt (+121/-0)
mailman/tests/bounces/dsn_08.txt (+131/-0)
mailman/tests/bounces/dsn_09.txt (+85/-0)
mailman/tests/bounces/dsn_10.txt (+66/-0)
mailman/tests/bounces/dsn_11.txt (+176/-0)
mailman/tests/bounces/dsn_12.txt (+40/-0)
mailman/tests/bounces/dsn_13.txt (+311/-0)
mailman/tests/bounces/dsn_14.txt (+149/-0)
mailman/tests/bounces/dsn_15.txt (+278/-0)
mailman/tests/bounces/dumbass_01.txt (+109/-0)
mailman/tests/bounces/exim_01.txt (+58/-0)
mailman/tests/bounces/groupwise_01.txt (+151/-0)
mailman/tests/bounces/groupwise_02.txt (+186/-0)
mailman/tests/bounces/hotpop_01.txt (+180/-0)
mailman/tests/bounces/llnl_01.txt (+203/-0)
mailman/tests/bounces/microsoft_01.txt (+108/-0)
mailman/tests/bounces/microsoft_02.txt (+119/-0)
mailman/tests/bounces/microsoft_03.txt (+65/-0)
mailman/tests/bounces/netscape_01.txt (+123/-0)
mailman/tests/bounces/newmailru_01.txt (+112/-0)
mailman/tests/bounces/postfix_01.txt (+123/-0)
mailman/tests/bounces/postfix_02.txt (+60/-0)
mailman/tests/bounces/postfix_03.txt (+145/-0)
mailman/tests/bounces/postfix_04.txt (+240/-0)
mailman/tests/bounces/postfix_05.txt (+231/-0)
mailman/tests/bounces/qmail_01.txt (+103/-0)
mailman/tests/bounces/qmail_02.txt (+73/-0)
mailman/tests/bounces/qmail_03.txt (+245/-0)
mailman/tests/bounces/qmail_04.txt (+81/-0)
mailman/tests/bounces/qmail_05.txt (+121/-0)
mailman/tests/bounces/sendmail_01.txt (+146/-0)
mailman/tests/bounces/simple_01.txt (+153/-0)
mailman/tests/bounces/simple_02.txt (+118/-0)
mailman/tests/bounces/simple_03.txt (+68/-0)
mailman/tests/bounces/simple_04.txt (+105/-0)
mailman/tests/bounces/simple_05.txt (+81/-0)
mailman/tests/bounces/simple_06.txt (+77/-0)
mailman/tests/bounces/simple_07.txt (+21/-0)
mailman/tests/bounces/simple_08.txt (+81/-0)
mailman/tests/bounces/simple_09.txt (+27/-0)
mailman/tests/bounces/simple_10.txt (+45/-0)
mailman/tests/bounces/simple_11.txt (+68/-0)
mailman/tests/bounces/simple_12.txt (+81/-0)
mailman/tests/bounces/simple_13.txt (+60/-0)
mailman/tests/bounces/simple_14.txt (+122/-0)
mailman/tests/bounces/simple_15.txt (+259/-0)
mailman/tests/bounces/simple_16.txt (+78/-0)
mailman/tests/bounces/simple_17.txt (+76/-0)
mailman/tests/bounces/simple_18.txt (+73/-0)
mailman/tests/bounces/simple_19.txt (+77/-0)
mailman/tests/bounces/simple_20.txt (+27/-0)
mailman/tests/bounces/simple_21.txt (+46/-0)
mailman/tests/bounces/simple_22.txt (+25/-0)
mailman/tests/bounces/simple_23.txt (+152/-0)
mailman/tests/bounces/simple_24.txt (+33/-0)
mailman/tests/bounces/simple_25.txt (+379/-0)
mailman/tests/bounces/simple_26.txt (+74/-0)
mailman/tests/bounces/simple_27.txt (+279/-0)
mailman/tests/bounces/sina_01.txt (+128/-0)
mailman/tests/bounces/smtp32_01.txt (+97/-0)
mailman/tests/bounces/smtp32_02.txt (+96/-0)
mailman/tests/bounces/smtp32_03.txt (+92/-0)
mailman/tests/bounces/smtp32_04.txt (+47/-0)
mailman/tests/bounces/smtp32_05.txt (+63/-0)
mailman/tests/bounces/smtp32_06.txt (+41/-0)
mailman/tests/bounces/smtp32_07.txt (+81/-0)
mailman/tests/bounces/yahoo_01.txt (+47/-0)
mailman/tests/bounces/yahoo_02.txt (+57/-0)
mailman/tests/bounces/yahoo_03.txt (+98/-0)
mailman/tests/bounces/yahoo_04.txt (+150/-0)
mailman/tests/bounces/yahoo_05.txt (+150/-0)
mailman/tests/bounces/yahoo_06.txt (+105/-0)
mailman/tests/bounces/yahoo_07.txt (+112/-0)
mailman/tests/bounces/yahoo_08.txt (+129/-0)
mailman/tests/bounces/yahoo_09.txt (+165/-0)
mailman/tests/bounces/yahoo_10.txt (+83/-0)
mailman/tests/bounces/yale_01.txt (+422/-0)
mailman/tests/helpers.py (+237/-0)
mailman/tests/smtplistener.py (+86/-0)
mailman/tests/test_bounces.py (+223/-0)
mailman/tests/test_documentation.py (+121/-0)
mailman/tests/test_membership.py (+386/-0)
mailman/tests/test_passwords.py (+161/-0)
mailman/tests/test_safedict.py (+48/-0)
mailman/tests/test_security_mgr.py (+233/-0)
mailman/tests/testing.cfg.in (+14/-0)
mailman/version.py (+48/-0)
scripts/driver (+314/-0)
setup.cfg (+5/-0)
setup.py (+104/-0)
staging/bin/add_members (+9/-0)
staging/bin/arch (+9/-0)
staging/bin/bounces (+9/-0)
staging/bin/bumpdigests (+9/-0)
staging/bin/change_pw (+9/-0)
staging/bin/check_perms (+9/-0)
staging/bin/checkdbs (+9/-0)
staging/bin/cleanarch (+9/-0)
staging/bin/config_list (+9/-0)
staging/bin/confirm (+9/-0)
staging/bin/create_list (+9/-0)
staging/bin/disabled (+9/-0)
staging/bin/dumpdb (+9/-0)
staging/bin/export (+9/-0)
staging/bin/find_member (+9/-0)
staging/bin/gate_news (+9/-0)
staging/bin/genaliases (+9/-0)
staging/bin/import (+9/-0)
staging/bin/inject (+9/-0)
staging/bin/join (+9/-0)
staging/bin/leave (+9/-0)
staging/bin/list_lists (+9/-0)
staging/bin/list_members (+9/-0)
staging/bin/list_owners (+9/-0)
staging/bin/mailmanctl (+9/-0)
staging/bin/make_instance (+9/-0)
staging/bin/master (+9/-0)
staging/bin/mmsitepass (+9/-0)
staging/bin/nightly_gzip (+9/-0)
staging/bin/owner (+9/-0)
staging/bin/post (+9/-0)
staging/bin/qrunner (+9/-0)
staging/bin/remove_list (+9/-0)
staging/bin/request (+9/-0)
staging/bin/senddigests (+9/-0)
staging/bin/set_members (+9/-0)
staging/bin/show_config (+9/-0)
staging/bin/show_qfiles (+9/-0)
staging/bin/testall (+9/-0)
staging/bin/unshunt (+9/-0)
staging/bin/update (+9/-0)
staging/bin/version (+9/-0)
staging/bin/withlist (+9/-0)
staging/easy-install.pth (+3/-0)
staging/mailman.egg-link (+2/-0)
staging/site.py (+82/-0)
tests/fblast.py (+60/-0)
tests/msgs/bad_01.txt (+62/-0)
tests/onebounce.py (+94/-0)
var/etc/mailman.cfg (+54/-0)
Conflict adding file README.txt.  Moved existing file to README.txt.moved.
Conflict adding file contrib.  Moved existing file to contrib.moved.
Conflict adding file cron.  Moved existing file to cron.moved.
Conflict adding file data.  Moved existing file to data.moved.
Conflict adding file setup.py.  Moved existing file to setup.py.moved.
To merge this branch: bzr merge lp:~mmlmtp/mailman/mm3lmtp
Reviewer Review Type Date Requested Status
Barry Warsaw Disapprove
Review via email: mp+38413@code.launchpad.net

Description of the change

Gives LMTP server the ability to reject mail to non-existent lists at LMTP time, and to reject mail from people who don't have permission to post to a list.

To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :

Yeah, there's something really wrong with this branch - it's trying to merge way too much. It was probably branched off of the wrong thing, or hasn't be updated in a long time. Since this branch is pretty old (my fault I'm sure) it may not be possible for William to reconcile it with current lp:mailman trunk. I'll see if I can pick out the relevant bits.

review: Needs Fixing
Revision history for this message
Barry Warsaw (barry) wrote :

I'm going to close this as rejected since there are so many conflicts. Please do consider resubmitting against our gitlab project: https://gitlab.com/mailman/mailman

review: Disapprove

Unmerged revisions

11. By iane <email address hidden>

Removed extraneous log files

10. By William Mead <email address hidden>

rev10: Added a method to Utils, Added some tests to the lmtp Doctest

9. By William Mead <email address hidden>

rev9: Added PIPELINING supported message at LHLO

8. By William Mead <email address hidden>

rev8:fixed Doctest, code respects rfcs

7. By William Mead <email address hidden>

rev7: Complete LMTP channel, Fully uses enhanced error codes, LHLO command is required before MAIL command

6. By William Mead <email address hidden>

rev6: Fixed Doctest

5. By William Mead <email address hidden>

rev5: Added two new enhanced error codes

4. By William Mead <email address hidden>

rev4: Created three new methods for getting and parsing listnames, accept_these_nonmembers will be checked

3. By William Mead <email address hidden>

rev3 : Added enhanced error codes, Sender address can be rejected at RCPT TO

2. By William Mead <email address hidden>

rev2

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'PKG-INFO'
2--- PKG-INFO 1970-01-01 00:00:00 +0000
3+++ PKG-INFO 2010-10-14 12:15:59 +0000
4@@ -0,0 +1,14 @@
5+Metadata-Version: 1.0
6+Name: mailman
7+Version: 3.0.0a1
8+Summary: Mailman -- the GNU mailing list manager
9+Home-page: http://www.list.org
10+Author: The Mailman Developers
11+Author-email: mailman-developers@python.org
12+License: GPL
13+Description: This is GNU Mailman, a mailing list management system distributed under the
14+ terms of the GNU General Public License (GPL). The name of this software is
15+ spelled 'Mailman' with a leading capital 'M' but with a lower case second `m'.
16+ Any other spelling is incorrect.
17+Keywords: email
18+Platform: UNKNOWN
19
20=== added file 'README.txt'
21--- README.txt 1970-01-01 00:00:00 +0000
22+++ README.txt 2010-10-14 12:15:59 +0000
23@@ -0,0 +1,197 @@
24+Mailman - The GNU Mailing List Management System
25+Copyright (C) 1998-2008 by the Free Software Foundation, Inc.
26+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27+
28+INTRODUCTION
29+
30+ This is GNU Mailman, a mailing list management system distributed under
31+ the terms of the GNU General Public License (GPL). The name of this
32+ software is spelled "Mailman" with a leading capital `M' but with a lower
33+ case second `m'. Any other spelling is incorrect.
34+
35+ Mailman is written in Python, a free object-oriented scripting language.
36+ Python is available for all platforms that Mailman is supported on, which
37+ includes GNU/Linux and most other Unix-like operating systems
38+ (e.g. Solaris, *BSD, MacOSX, etc.). It does not run on Windows, although
39+ web and mail clients on any platform should be able to interact with
40+ Mailman just fine.
41+
42+ Mailman was originally developed by John Viega. Subsequent development
43+ (through version 1.0b3) was by Ken Manheimer. Further work towards the
44+ 1.0 final release was a group effort, with the core contributors being:
45+ Barry Warsaw, Ken Manheimer, Scott Cotton, Harald Meland, and John Viega.
46+ Version 1.0 and beyond have been primarily maintained by Barry Warsaw with
47+ contributions from many; see the ACKNOWLEDGMENTS file for details. Jeremy
48+ Hylton helped considerably with the Pipermail code in Mailman 2.0.
49+ Mailman 2.1 is now being primarily maintained by Mark Sapiro and Tokio
50+ Kikuchi. Barry Warsaw is the lead developer on Mailman 3.
51+
52+ The Mailman home page is:
53+
54+ http://www.list.org
55+
56+ with mirrors at:
57+
58+ http://www.gnu.org/software/mailman
59+ http://mailman.sf.net
60+
61+ You might also be interested in the Mailman wiki at:
62+
63+ http://wiki.list.org
64+
65+ Mailman 3.0 requires Python 2.5 or greater, which can be downloaded from:
66+
67+ http://www.python.org
68+
69+ It is recommended that you use at least Python 2.5.2, the latest release
70+ as of this writing (31-Mar-2008).
71+
72+
73+FEATURES
74+
75+ **Mailman 3.0 is alpha software and some of this information may be out of
76+ date or not currently working. This will improve as the alpha releases
77+ are developed.**
78+
79+ Mailman has most of the standard features you'd expect in a mailing list
80+ manager, and more:
81+
82+ - Web based list administration for nearly all tasks. Web based
83+ subscriptions and user configuration management. A customizable "home
84+ page" for each mailing list.
85+
86+ - Privacy features such as moderation, open and closed list subscription
87+ policies, private membership rosters, and sender-based filters.
88+
89+ - Automatic web based archiving built-in with support for private and
90+ public archives, and hooks for external archivers.
91+
92+ - Per-user configuration optional digest delivery for either
93+ MIME-compliant or RFC 1153 style "plain text" digests.
94+
95+ - Integrated mail/Usenet gateways.
96+
97+ - Integrated auto-replies.
98+
99+ - Email commands.
100+
101+ - Integrated bounce detection within an extensible framework.
102+
103+ - Integrated spam detection, and MIME-based content filtering.
104+
105+ - An extensible mail delivery pipeline.
106+
107+ - Support for virtual domains.
108+
109+
110+REQUIREMENTS
111+
112+ The default mail delivery mechanism uses a direct SMTP connection to
113+ whatever mail transport agent you have running on port 25. You can thus
114+ use Mailman with any such MTA, however with certain MTAs (e.g. Exim and
115+ Postfix), Mailman will support thru-the-web creation and removal of
116+ mailing lists.
117+
118+ Mailman works with any web server that supports CGI/1.1. The HTML it
119+ generates should be friendly to most web browsers and network connections.
120+
121+ You will need root access on the machine hosting your Mailman installation
122+ in order to complete some of the configuration steps. See the INSTALL.txt
123+ file for details.
124+
125+ Mailman's web and email user interface should be compatible with just
126+ about any mail reader or web browser, although a mail reader that is MIME
127+ aware will be a big help. You do not need Java, JavaScript, or any other
128+ fancy plugins.
129+
130+
131+FOR MORE INFORMATION
132+
133+ For information on this alpha release, see docs/ALPHA.txt
134+
135+ More documentation is available in the docs directory, and on-line (see
136+ below). Installation instructions are contained in the
137+ docs/readmes/INSTALL.txt file. Upgrading information is available in the
138+ docs/readmes/UPGRADING.txt file. See the docs/NEWS.txt file for a list of
139+ changes since version 0.9.
140+
141+ The online documentation can be found in
142+
143+ file:admin/www/index.html
144+
145+ in the directory in which you unpacked Mailman.
146+
147+ There is an online FAQ maintained by the Mailman community, which contains
148+ a vast amount of information:
149+
150+ http://www.python.org/cgi-bin/faqw-mm.py
151+
152+ There is also a wiki for more community-driven information:
153+
154+ http://wiki.list.org
155+
156+ Chris Kolar has made a list owner-oriented manual available from
157+ the following URL
158+
159+ http://www.imsa.edu/~ckolar/mailman/
160+
161+ There are also several mailing lists that can be used as resources
162+ to help you get going with Mailman.
163+
164+ Mailman-Users
165+ An list for users of Mailman, for posting questions or problems
166+ related to installation, use, etc. We'll try to keep the deep
167+ technical discussions off this list.
168+
169+ http://mail.python.org/mailman/listinfo/mailman-users
170+
171+ Listowners
172+ This mailing list with a non-technical focus, specifically for
173+ discussions from the perspective of listowners and moderators who do
174+ not have "shell access" to the mailing list server where the Mailman
175+ software runs.
176+
177+ http://listowner.org
178+
179+ Mailman-Announce
180+ A read-only list for release announcements an other important news.
181+
182+ http://mail.python.org/mailman/listinfo/mailman-announce
183+
184+ Mailman-Developers
185+ A list for those of you interested in helping develop Mailman 2's
186+ future direction. This list will contain in-depth technical
187+ discussions.
188+
189+ http://mail.python.org/mailman/listinfo/mailman-developers
190+
191+ Mailman3-Dev
192+ Get involved now in the development of Mailman 3!
193+
194+ http://mail.python.org/mailman/listinfo/mailman3-dev
195+
196+ Mailman-I18N
197+ A list for the discussion of the Mailman internationalization
198+ effort. Mailman 2.1 is fully multi-lingual.
199+
200+ http://mail.python.org/mailman/listinfo/mailman-i18n
201+
202+ Mailman-Checkins
203+ A read-only list which is an adjunct to the public anonymous CVS
204+ repository. You can stay on the bleeding edge of Mailman development
205+ by subscribing to this list.
206+
207+ http://mail.python.org/mailman/listinfo/mailman-checkins
208+
209+ The Mailman project is coordinated on SourceForge at
210+
211+ http://sf.net/projects/mailman
212+
213+ You should use SourceForge to report bugs and to upload patches.
214+
215+
216+
217
218+Local Variables:
219+mode: indented-text
220+indent-tabs-mode: nil
221+End:
222
223=== renamed file 'README.txt' => 'README.txt.moved'
224=== added file 'TODO.txt'
225--- TODO.txt 1970-01-01 00:00:00 +0000
226+++ TODO.txt 2010-10-14 12:15:59 +0000
227@@ -0,0 +1,15 @@
228+This list is by no means complete. I'm just using it to track short term
229+things that I need to do.
230+
231+Get rid of PickleTypes
232+Get rid of MailList class! (done for test suite!)
233+Add tests for bin/newlist and bin/rmlist
234+Add tests for plugins
235+Rework MTA plugins and add tests
236+Address XXX and FIXME
237+Fix the roster creation cruft for mailing lists
238+Suss out the IDomain stuff
239+Remove Date: header from messagestore requirements (see list thread)
240+Handle moderation flag (see Mailman.app.membership)
241+Eradicate MailList.Lock() and friends.
242+Eradicate MemberAdapter and friends.
243
244=== added directory 'bin'
245=== added file 'bin/clone_member'
246--- bin/clone_member 1970-01-01 00:00:00 +0000
247+++ bin/clone_member 2010-10-14 12:15:59 +0000
248@@ -0,0 +1,219 @@
249+#! @PYTHON@
250+#
251+# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
252+#
253+# This program is free software; you can redistribute it and/or
254+# modify it under the terms of the GNU General Public License
255+# as published by the Free Software Foundation; either version 2
256+# of the License, or (at your option) any later version.
257+#
258+# This program is distributed in the hope that it will be useful,
259+# but WITHOUT ANY WARRANTY; without even the implied warranty of
260+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
261+# GNU General Public License for more details.
262+#
263+# You should have received a copy of the GNU General Public License
264+# along with this program; if not, write to the Free Software
265+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
266+
267+"""Clone a member address.
268+
269+Cloning a member address means that a new member will be added who has all the
270+same options and passwords as the original member address. Note that this
271+operation is fairly trusting of the user who runs it -- it does no
272+verification to the new address, it does not send out a welcome message, etc.
273+
274+The existing member's subscription is usually not modified in any way. If you
275+want to remove the old address, use the -r flag. If you also want to change
276+any list admin addresses, use the -a flag.
277+
278+Usage:
279+ clone_member [options] fromoldaddr tonewaddr
280+
281+Where:
282+
283+ --listname=listname
284+ -l listname
285+ Check and modify only the named mailing lists. If -l is not given,
286+ then all mailing lists are scanned from the address. Multiple -l
287+ options can be supplied.
288+
289+ --remove
290+ -r
291+ Remove the old address from the mailing list after it's been cloned.
292+
293+ --admin
294+ -a
295+ Scan the list admin addresses for the old address, and clone or change
296+ them too.
297+
298+ --quiet
299+ -q
300+ Do the modifications quietly.
301+
302+ --nomodify
303+ -n
304+ Print what would be done, but don't actually do it. Inhibits the
305+ --quiet flag.
306+
307+ --help
308+ -h
309+ Print this help message and exit.
310+
311+ fromoldaddr (`from old address') is the old address of the user. tonewaddr
312+ (`to new address') is the new address of the user.
313+
314+"""
315+
316+import sys
317+import getopt
318+
319+import paths
320+from Mailman import MailList
321+from Mailman import Utils
322+from Mailman import Errors
323+from Mailman.i18n import _
324+
325+
326+
327
328+def usage(code, msg=''):
329+ if code:
330+ fd = sys.stderr
331+ else:
332+ fd = sys.stdout
333+ print >> fd, _(__doc__)
334+ if msg:
335+ print >> fd, msg
336+ sys.exit(code)
337+
338+
339+
340
341+def dolist(mlist, options):
342+ SPACE = ' '
343+ if not options.quiet:
344+ print _('processing mailing list:'), mlist.internal_name()
345+
346+ # scan the list owners. TBD: mlist.owner keys should be lowercase?
347+ oldowners = mlist.owner[:]
348+ oldowners.sort()
349+ if options.admintoo:
350+ if not options.quiet:
351+ print _(' scanning list owners:'), SPACE.join(oldowners)
352+ newowners = {}
353+ foundp = 0
354+ for owner in mlist.owner:
355+ if options.lfromaddr == owner.lower():
356+ foundp = 1
357+ if options.remove:
358+ continue
359+ newowners[owner] = 1
360+ if foundp:
361+ newowners[options.toaddr] = 1
362+ newowners = newowners.keys()
363+ newowners.sort()
364+ if options.modify:
365+ mlist.owner = newowners
366+ if not options.quiet:
367+ if newowners <> oldowners:
368+ print
369+ print _(' new list owners:'), SPACE.join(newowners)
370+ else:
371+ print _('(no change)')
372+
373+ # see if the fromaddr is a digest member or regular member
374+ if options.lfromaddr in mlist.getDigestMemberKeys():
375+ digest = 1
376+ elif options.lfromaddr in mlist.getRegularMemberKeys():
377+ digest = 0
378+ else:
379+ if not options.quiet:
380+ print _(' address not found:'), options.fromaddr
381+ return
382+
383+ # Now change the membership address
384+ try:
385+ if options.modify:
386+ mlist.changeMemberAddress(options.fromaddr, options.toaddr,
387+ not options.remove)
388+ if not options.quiet:
389+ print _(' clone address added:'), options.toaddr
390+ except Errors.MMAlreadyAMember:
391+ if not options.quiet:
392+ print _(' clone address is already a member:'), options.toaddr
393+
394+ if options.remove:
395+ print _(' original address removed:'), options.fromaddr
396+
397+
398+
399
400+def main():
401+ # default options
402+ class Options:
403+ listnames = None
404+ remove = 0
405+ admintoo = 0
406+ quiet = 0
407+ modify = 1
408+
409+ # scan sysargs
410+ try:
411+ opts, args = getopt.getopt(
412+ sys.argv[1:], 'arl:qnh',
413+ ['admin', 'remove', 'listname=', 'quiet', 'nomodify', 'help'])
414+ except getopt.error, msg:
415+ usage(1, msg)
416+
417+ options = Options()
418+ for opt, arg in opts:
419+ if opt in ('-h', '--help'):
420+ usage(0)
421+ elif opt in ('-q', '--quiet'):
422+ options.quiet = 1
423+ elif opt in ('-n', '--nomodify'):
424+ options.modify = 0
425+ elif opt in ('-a', '--admin'):
426+ options.admintoo = 1
427+ elif opt in ('-r', '--remove'):
428+ options.remove = 1
429+ elif opt in ('-l', '--listname'):
430+ if options.listnames is None:
431+ options.listnames = []
432+ options.listnames.append(arg.lower())
433+
434+ # further options and argument processing
435+ if not options.modify:
436+ options.quiet = 0
437+
438+ if len(args) <> 2:
439+ usage(1)
440+ fromaddr = args[0]
441+ toaddr = args[1]
442+
443+ # validate and normalize the target address
444+ try:
445+ Utils.ValidateEmail(toaddr)
446+ except Errors.EmailAddressError:
447+ usage(1, _('Not a valid email address: %(toaddr)s'))
448+ lfromaddr = fromaddr.lower()
449+ options.toaddr = toaddr
450+ options.fromaddr = fromaddr
451+ options.lfromaddr = lfromaddr
452+
453+ if options.listnames is None:
454+ options.listnames = Utils.list_names()
455+
456+ for listname in options.listnames:
457+ try:
458+ mlist = MailList.MailList(listname)
459+ except Errors.MMListError, e:
460+ print _('Error opening list "%(listname)s", skipping.\n%(e)s')
461+ continue
462+ try:
463+ dolist(mlist, options)
464+ finally:
465+ mlist.Save()
466+ mlist.Unlock()
467+
468+
469
470+if __name__ == '__main__':
471+ main()
472
473=== added file 'bin/discard'
474--- bin/discard 1970-01-01 00:00:00 +0000
475+++ bin/discard 2010-10-14 12:15:59 +0000
476@@ -0,0 +1,120 @@
477+#! @PYTHON@
478+#
479+# Copyright (C) 2003 by the Free Software Foundation, Inc.
480+#
481+# This program is free software; you can redistribute it and/or
482+# modify it under the terms of the GNU General Public License
483+# as published by the Free Software Foundation; either version 2
484+# of the License, or (at your option) any later version.
485+#
486+# This program is distributed in the hope that it will be useful,
487+# but WITHOUT ANY WARRANTY; without even the implied warranty of
488+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
489+# GNU General Public License for more details.
490+#
491+# You should have received a copy of the GNU General Public License
492+# along with this program; if not, write to the Free Software
493+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
494+
495+"""Discard held messages.
496+
497+Usage:
498+ discard [options] file ...
499+
500+Options:
501+ --help / -h
502+ Print this help message and exit.
503+
504+ --quiet / -q
505+ Don't print status messages.
506+"""
507+
508+# TODO: add command line arguments for specifying other actions than DISCARD,
509+# and also for specifying other __handlepost() arguments, i.e. comment,
510+# preserve, forward, addr
511+
512+import os
513+import re
514+import sys
515+import getopt
516+
517+import paths
518+from Mailman import mm_cfg
519+from Mailman.MailList import MailList
520+from Mailman.i18n import _
521+
522+try:
523+ True, False
524+except NameError:
525+ True = 1
526+ False = 0
527+
528+cre = re.compile(r'heldmsg-(?P<listname>.*)-(?P<id>[0-9]+)\.(pck|txt)$')
529+
530+
531+
532
533+def usage(code, msg=''):
534+ if code:
535+ fd = sys.stderr
536+ else:
537+ fd = sys.stdout
538+ print >> fd, _(__doc__)
539+ if msg:
540+ print >> fd, msg
541+ sys.exit(code)
542+
543+
544+
545
546+def main():
547+ try:
548+ opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet'])
549+ except getopt.error, msg:
550+ usage(1, msg)
551+
552+ quiet = False
553+ for opt, arg in opts:
554+ if opt in ('-h', '--help'):
555+ usage(0)
556+ elif opt in ('-q', '--quiet'):
557+ quiet = True
558+
559+ files = args
560+ if not files:
561+ print _('Nothing to do.')
562+
563+ # Mapping from listnames to sequence of request ids
564+ discards = {}
565+
566+ # Cruise through all the named files, collating by mailing list. We'll
567+ # lock the list once, process all holds for that list and move on.
568+ for f in files:
569+ basename = os.path.basename(f)
570+ mo = cre.match(basename)
571+ if not mo:
572+ print >> sys.stderr, _('Ignoring non-held message: %(f)s')
573+ continue
574+ listname, id = mo.group('listname', 'id')
575+ try:
576+ id = int(id)
577+ except (ValueError, TypeError):
578+ print >> sys.stderr, _('Ignoring held msg w/bad id: %(f)s')
579+ continue
580+ discards.setdefault(listname, []).append(id)
581+
582+ # Now do the discards
583+ for listname, ids in discards.items():
584+ mlist = MailList(listname)
585+ try:
586+ for id in ids:
587+ # No comment, no preserve, no forward, no forwarding address
588+ mlist.HandleRequest(id, mm_cfg.DISCARD, '', False, False, '')
589+ if not quiet:
590+ print _('Discarded held msg #%(id)s for list %(listname)s')
591+ mlist.Save()
592+ finally:
593+ mlist.Unlock()
594+
595+
596+
597
598+if __name__ == '__main__':
599+ main()
600
601=== added file 'bin/fix_url.py'
602--- bin/fix_url.py 1970-01-01 00:00:00 +0000
603+++ bin/fix_url.py 2010-10-14 12:15:59 +0000
604@@ -0,0 +1,93 @@
605+#! @PYTHON@
606+#
607+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
608+#
609+# This program is free software; you can redistribute it and/or
610+# modify it under the terms of the GNU General Public License
611+# as published by the Free Software Foundation; either version 2
612+# of the License, or (at your option) any later version.
613+#
614+# This program is distributed in the hope that it will be useful,
615+# but WITHOUT ANY WARRANTY; without even the implied warranty of
616+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
617+# GNU General Public License for more details.
618+#
619+# You should have received a copy of the GNU General Public License
620+# along with this program; if not, write to the Free Software
621+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
622+# USA.
623+
624+"""Reset a list's web_page_url attribute to the default setting.
625+
626+This script is intended to be run as a bin/withlist script, i.e.
627+
628+% bin/withlist -l -r fix_url listname [options]
629+
630+Options:
631+ -u urlhost
632+ --urlhost=urlhost
633+ Look up urlhost in the virtual host table and set the web_page_url and
634+ host_name attributes of the list to the values found. This
635+ essentially moves the list from one virtual domain to another.
636+
637+ Without this option, the default web_page_url and host_name values are
638+ used.
639+
640+ -v / --verbose
641+ Print what the script is doing.
642+
643+If run standalone, it prints this help text and exits.
644+"""
645+
646+import sys
647+import getopt
648+
649+import paths
650+from Mailman.configuration import config
651+from Mailman.i18n import _
652+
653+
654+
655
656+def usage(code, msg=''):
657+ print _(__doc__.replace('%', '%%'))
658+ if msg:
659+ print msg
660+ sys.exit(code)
661+
662+
663+
664
665+def fix_url(mlist, *args):
666+ try:
667+ opts, args = getopt.getopt(args, 'u:v', ['urlhost=', 'verbose'])
668+ except getopt.error, msg:
669+ usage(1, msg)
670+
671+ verbose = 0
672+ urlhost = mailhost = None
673+ for opt, arg in opts:
674+ if opt in ('-u', '--urlhost'):
675+ urlhost = arg
676+ elif opt in ('-v', '--verbose'):
677+ verbose = 1
678+
679+ if urlhost:
680+ web_page_url = config.DEFAULT_URL_PATTERN % urlhost
681+ mailhost = config.VIRTUAL_HOSTS.get(urlhost.lower(), urlhost)
682+ else:
683+ web_page_url = config.DEFAULT_URL_PATTERN % config.DEFAULT_URL_HOST
684+ mailhost = config.DEFAULT_EMAIL_HOST
685+
686+ if verbose:
687+ print _('Setting web_page_url to: %(web_page_url)s')
688+ mlist.web_page_url = web_page_url
689+ if verbose:
690+ print _('Setting host_name to: %(mailhost)s')
691+ mlist.host_name = mailhost
692+ print _('Saving list')
693+ mlist.Save()
694+ mlist.Unlock()
695+
696+
697+
698
699+if __name__ == '__main__':
700+ usage(0)
701
702=== added file 'bin/list_admins'
703--- bin/list_admins 1970-01-01 00:00:00 +0000
704+++ bin/list_admins 2010-10-14 12:15:59 +0000
705@@ -0,0 +1,101 @@
706+#! @PYTHON@
707+#
708+# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
709+#
710+# This program is free software; you can redistribute it and/or
711+# modify it under the terms of the GNU General Public License
712+# as published by the Free Software Foundation; either version 2
713+# of the License, or (at your option) any later version.
714+#
715+# This program is distributed in the hope that it will be useful,
716+# but WITHOUT ANY WARRANTY; without even the implied warranty of
717+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
718+# GNU General Public License for more details.
719+#
720+# You should have received a copy of the GNU General Public License
721+# along with this program; if not, write to the Free Software
722+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
723+
724+"""List all the owners of a mailing list.
725+
726+Usage: %(program)s [options] listname ...
727+
728+Where:
729+
730+ --all-vhost=vhost
731+ -v=vhost
732+ List the owners of all the mailing lists for the given virtual host.
733+
734+ --all
735+ -a
736+ List the owners of all the mailing lists on this system.
737+
738+ --help
739+ -h
740+ Print this help message and exit.
741+
742+`listname' is the name of the mailing list to print the owners of. You can
743+have more than one named list on the command line.
744+"""
745+
746+import sys
747+import getopt
748+
749+import paths
750+from Mailman import MailList, Utils
751+from Mailman import Errors
752+from Mailman.i18n import _
753+
754+COMMASPACE = ', '
755+
756+program = sys.argv[0]
757+
758+
759+
760
761+def usage(code, msg=''):
762+ if code:
763+ fd = sys.stderr
764+ else:
765+ fd = sys.stdout
766+ print >> fd, _(__doc__)
767+ if msg:
768+ print >> fd, msg
769+ sys.exit(code)
770+
771+
772+
773
774+def main():
775+ try:
776+ opts, args = getopt.getopt(sys.argv[1:], 'hv:a',
777+ ['help', 'all-vhost=', 'all'])
778+ except getopt.error, msg:
779+ usage(1, msg)
780+
781+ listnames = args
782+ vhost = None
783+ for opt, arg in opts:
784+ if opt in ('-h', '--help'):
785+ usage(0)
786+ elif opt in ('-a', '--all'):
787+ listnames = Utils.list_names()
788+ elif opt in ('-v', '--all-vhost'):
789+ listnames = Utils.list_names()
790+ vhost = arg
791+
792+ for listname in listnames:
793+ try:
794+ mlist = MailList.MailList(listname, lock=0)
795+ except Errors.MMListError, e:
796+ print _('No such list: %(listname)s')
797+ continue
798+
799+ if vhost and vhost <> mlist.host_name:
800+ continue
801+
802+ owners = COMMASPACE.join(mlist.owner)
803+ print _('List: %(listname)s, \tOwners: %(owners)s')
804+
805+
806+
807
808+if __name__ == '__main__':
809+ main()
810
811=== added file 'bin/msgfmt.py'
812--- bin/msgfmt.py 1970-01-01 00:00:00 +0000
813+++ bin/msgfmt.py 2010-10-14 12:15:59 +0000
814@@ -0,0 +1,203 @@
815+#! /usr/bin/env python
816+# -*- coding: iso-8859-1 -*-
817+# Written by Martin v. Löwis <loewis@informatik.hu-berlin.de>
818+
819+"""Generate binary message catalog from textual translation description.
820+
821+This program converts a textual Uniforum-style message catalog (.po file) into
822+a binary GNU catalog (.mo file). This is essentially the same function as the
823+GNU msgfmt program, however, it is a simpler implementation.
824+
825+Usage: msgfmt.py [OPTIONS] filename.po
826+
827+Options:
828+ -o file
829+ --output-file=file
830+ Specify the output file to write to. If omitted, output will go to a
831+ file named filename.mo (based off the input file name).
832+
833+ -h
834+ --help
835+ Print this message and exit.
836+
837+ -V
838+ --version
839+ Display version information and exit.
840+"""
841+
842+import sys
843+import os
844+import getopt
845+import struct
846+import array
847+
848+__version__ = "1.1"
849+
850+MESSAGES = {}
851+
852+
853+
854
855+def usage(code, msg=''):
856+ print >> sys.stderr, __doc__
857+ if msg:
858+ print >> sys.stderr, msg
859+ sys.exit(code)
860+
861+
862+
863
864+def add(id, str, fuzzy):
865+ "Add a non-fuzzy translation to the dictionary."
866+ global MESSAGES
867+ if not fuzzy and str:
868+ MESSAGES[id] = str
869+
870+
871+
872
873+def generate():
874+ "Return the generated output."
875+ global MESSAGES
876+ keys = MESSAGES.keys()
877+ # the keys are sorted in the .mo file
878+ keys.sort()
879+ offsets = []
880+ ids = strs = ''
881+ for id in keys:
882+ # For each string, we need size and file offset. Each string is NUL
883+ # terminated; the NUL does not count into the size.
884+ offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))
885+ ids += id + '\0'
886+ strs += MESSAGES[id] + '\0'
887+ output = ''
888+ # The header is 7 32-bit unsigned integers. We don't use hash tables, so
889+ # the keys start right after the index tables.
890+ # translated string.
891+ keystart = 7*4+16*len(keys)
892+ # and the values start after the keys
893+ valuestart = keystart + len(ids)
894+ koffsets = []
895+ voffsets = []
896+ # The string table first has the list of keys, then the list of values.
897+ # Each entry has first the size of the string, then the file offset.
898+ for o1, l1, o2, l2 in offsets:
899+ koffsets += [l1, o1+keystart]
900+ voffsets += [l2, o2+valuestart]
901+ offsets = koffsets + voffsets
902+ output = struct.pack("Iiiiiii",
903+ 0x950412deL, # Magic
904+ 0, # Version
905+ len(keys), # # of entries
906+ 7*4, # start of key index
907+ 7*4+len(keys)*8, # start of value index
908+ 0, 0) # size and offset of hash table
909+ output += array.array("i", offsets).tostring()
910+ output += ids
911+ output += strs
912+ return output
913+
914+
915+
916
917+def make(filename, outfile):
918+ ID = 1
919+ STR = 2
920+
921+ # Compute .mo name from .po name and arguments
922+ if filename.endswith('.po'):
923+ infile = filename
924+ else:
925+ infile = filename + '.po'
926+ if outfile is None:
927+ outfile = os.path.splitext(infile)[0] + '.mo'
928+
929+ try:
930+ lines = open(infile).readlines()
931+ except IOError, msg:
932+ print >> sys.stderr, msg
933+ sys.exit(1)
934+
935+ section = None
936+ fuzzy = 0
937+
938+ # Parse the catalog
939+ lno = 0
940+ for l in lines:
941+ lno += 1
942+ # If we get a comment line after a msgstr, this is a new entry
943+ if l[0] == '#' and section == STR:
944+ add(msgid, msgstr, fuzzy)
945+ section = None
946+ fuzzy = 0
947+ # Record a fuzzy mark
948+ if l[:2] == '#,' and l.find('fuzzy'):
949+ fuzzy = 1
950+ # Skip comments
951+ if l[0] == '#':
952+ continue
953+ # Now we are in a msgid section, output previous section
954+ if l.startswith('msgid'):
955+ if section == STR:
956+ add(msgid, msgstr, fuzzy)
957+ section = ID
958+ l = l[5:]
959+ msgid = msgstr = ''
960+ # Now we are in a msgstr section
961+ elif l.startswith('msgstr'):
962+ section = STR
963+ l = l[6:]
964+ # Skip empty lines
965+ l = l.strip()
966+ if not l:
967+ continue
968+ # XXX: Does this always follow Python escape semantics?
969+ l = eval(l)
970+ if section == ID:
971+ msgid += l
972+ elif section == STR:
973+ msgstr += l
974+ else:
975+ print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \
976+ 'before:'
977+ print >> sys.stderr, l
978+ sys.exit(1)
979+ # Add last entry
980+ if section == STR:
981+ add(msgid, msgstr, fuzzy)
982+
983+ # Compute output
984+ output = generate()
985+
986+ try:
987+ open(outfile,"wb").write(output)
988+ except IOError,msg:
989+ print >> sys.stderr, msg
990+
991+
992+
993
994+def main():
995+ try:
996+ opts, args = getopt.getopt(sys.argv[1:], 'hVo:',
997+ ['help', 'version', 'output-file='])
998+ except getopt.error, msg:
999+ usage(1, msg)
1000+
1001+ outfile = None
1002+ # parse options
1003+ for opt, arg in opts:
1004+ if opt in ('-h', '--help'):
1005+ usage(0)
1006+ elif opt in ('-V', '--version'):
1007+ print >> sys.stderr, "msgfmt.py", __version__
1008+ sys.exit(0)
1009+ elif opt in ('-o', '--output-file'):
1010+ outfile = arg
1011+ # do it
1012+ if not args:
1013+ print >> sys.stderr, 'No input file given'
1014+ print >> sys.stderr, "Try `msgfmt --help' for more information."
1015+ return
1016+
1017+ for filename in args:
1018+ make(filename, outfile)
1019+
1020+
1021+if __name__ == '__main__':
1022+ main()
1023
1024=== added file 'bin/po2templ.py'
1025--- bin/po2templ.py 1970-01-01 00:00:00 +0000
1026+++ bin/po2templ.py 2010-10-14 12:15:59 +0000
1027@@ -0,0 +1,90 @@
1028+#! @PYTHON@
1029+#
1030+# Copyright (C) 2005-2007 by the Free Software Foundation, Inc.
1031+#
1032+# This program is free software; you can redistribute it and/or
1033+# modify it under the terms of the GNU General Public License
1034+# as published by the Free Software Foundation; either version 2
1035+# of the License, or (at your option) any later version.
1036+#
1037+# This program is distributed in the hope that it will be useful,
1038+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1039+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1040+# GNU General Public License for more details.
1041+#
1042+# You should have received a copy of the GNU General Public License
1043+# along with this program; if not, write to the Free Software
1044+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
1045+# USA.
1046+
1047+# Author: Tokio Kikuchi <tkikuchi@is.kochi-u.ac.jp>
1048+
1049+
1050+"""po2templ.py
1051+
1052+Extract templates from language po file.
1053+
1054+Usage: po2templ.py languages
1055+"""
1056+
1057+import re
1058+import sys
1059+
1060+cre = re.compile('^#:\s*templates/en/(?P<filename>.*?):1')
1061+
1062+
1063+
1064
1065+def do_lang(lang):
1066+ in_template = False
1067+ in_msg = False
1068+ msgstr = ''
1069+ fp = file('messages/%s/LC_MESSAGES/mailman.po' % lang)
1070+ try:
1071+ for line in fp:
1072+ m = cre.search(line)
1073+ if m:
1074+ in_template = True
1075+ in_msg = False
1076+ filename = m.group('filename')
1077+ outfilename = 'templates/%s/%s' % (lang, filename)
1078+ continue
1079+ if in_template and line.startswith('#,'):
1080+ if line.strip() == '#, fuzzy':
1081+ in_template = False
1082+ continue
1083+ if in_template and line.startswith('msgstr'):
1084+ line = line[7:]
1085+ in_msg = True
1086+ if in_msg:
1087+ if not line.strip():
1088+ in_template = False
1089+ in_msg = False
1090+ if len(msgstr) > 1 and outfilename:
1091+ # exclude no translation ... 1 is for LF only
1092+ outfile = file(outfilename, 'w')
1093+ try:
1094+ outfile.write(msgstr)
1095+ outfile.write('\n')
1096+ finally:
1097+ outfile.close()
1098+ outfilename = ''
1099+ msgstr = ''
1100+ continue
1101+ msgstr += eval(line)
1102+ finally:
1103+ fp.close()
1104+ if len(msgstr) > 1 and outfilename:
1105+ # flush remaining msgstr (last template file)
1106+ outfile = file(outfilename, 'w')
1107+ try:
1108+ outfile.write(msgstr)
1109+ outfile.write('\n')
1110+ finally:
1111+ outfile.close()
1112+
1113+
1114+
1115
1116+if __name__ == '__main__':
1117+ langs = sys.argv[1:]
1118+ for lang in langs:
1119+ do_lang(lang)
1120
1121=== added file 'bin/pygettext.py'
1122--- bin/pygettext.py 1970-01-01 00:00:00 +0000
1123+++ bin/pygettext.py 2010-10-14 12:15:59 +0000
1124@@ -0,0 +1,545 @@
1125+#! @PYTHON@
1126+# Originally written by Barry Warsaw <barry@zope.com>
1127+#
1128+# Minimally patched to make it even more xgettext compatible
1129+# by Peter Funk <pf@artcom-gmbh.de>
1130+
1131+"""pygettext -- Python equivalent of xgettext(1)
1132+
1133+Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the
1134+internationalization of C programs. Most of these tools are independent of
1135+the programming language and can be used from within Python programs. Martin
1136+von Loewis' work[1] helps considerably in this regard.
1137+
1138+There's one problem though; xgettext is the program that scans source code
1139+looking for message strings, but it groks only C (or C++). Python introduces
1140+a few wrinkles, such as dual quoting characters, triple quoted strings, and
1141+raw strings. xgettext understands none of this.
1142+
1143+Enter pygettext, which uses Python's standard tokenize module to scan Python
1144+source code, generating .pot files identical to what GNU xgettext[2] generates
1145+for C and C++ code. From there, the standard GNU tools can be used.
1146+
1147+A word about marking Python strings as candidates for translation. GNU
1148+xgettext recognizes the following keywords: gettext, dgettext, dcgettext, and
1149+gettext_noop. But those can be a lot of text to include all over your code.
1150+C and C++ have a trick: they use the C preprocessor. Most internationalized C
1151+source includes a #define for gettext() to _() so that what has to be written
1152+in the source is much less. Thus these are both translatable strings:
1153+
1154+ gettext("Translatable String")
1155+ _("Translatable String")
1156+
1157+Python of course has no preprocessor so this doesn't work so well. Thus,
1158+pygettext searches only for _() by default, but see the -k/--keyword flag
1159+below for how to augment this.
1160+
1161+ [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html
1162+ [2] http://www.gnu.org/software/gettext/gettext.html
1163+
1164+NOTE: pygettext attempts to be option and feature compatible with GNU xgettext
1165+where ever possible. However some options are still missing or are not fully
1166+implemented. Also, xgettext's use of command line switches with option
1167+arguments is broken, and in these cases, pygettext just defines additional
1168+switches.
1169+
1170+Usage: pygettext [options] inputfile ...
1171+
1172+Options:
1173+
1174+ -a
1175+ --extract-all
1176+ Extract all strings.
1177+
1178+ -d name
1179+ --default-domain=name
1180+ Rename the default output file from messages.pot to name.pot.
1181+
1182+ -E
1183+ --escape
1184+ Replace non-ASCII characters with octal escape sequences.
1185+
1186+ -D
1187+ --docstrings
1188+ Extract module, class, method, and function docstrings. These do not
1189+ need to be wrapped in _() markers, and in fact cannot be for Python to
1190+ consider them docstrings. (See also the -X option).
1191+
1192+ -h
1193+ --help
1194+ Print this help message and exit.
1195+
1196+ -k word
1197+ --keyword=word
1198+ Keywords to look for in addition to the default set, which are:
1199+ %(DEFAULTKEYWORDS)s
1200+
1201+ You can have multiple -k flags on the command line.
1202+
1203+ -K
1204+ --no-default-keywords
1205+ Disable the default set of keywords (see above). Any keywords
1206+ explicitly added with the -k/--keyword option are still recognized.
1207+
1208+ --no-location
1209+ Do not write filename/lineno location comments.
1210+
1211+ -n
1212+ --add-location
1213+ Write filename/lineno location comments indicating where each
1214+ extracted string is found in the source. These lines appear before
1215+ each msgid. The style of comments is controlled by the -S/--style
1216+ option. This is the default.
1217+
1218+ -o filename
1219+ --output=filename
1220+ Rename the default output file from messages.pot to filename. If
1221+ filename is `-' then the output is sent to standard out.
1222+
1223+ -p dir
1224+ --output-dir=dir
1225+ Output files will be placed in directory dir.
1226+
1227+ -S stylename
1228+ --style stylename
1229+ Specify which style to use for location comments. Two styles are
1230+ supported:
1231+
1232+ Solaris # File: filename, line: line-number
1233+ GNU #: filename:line
1234+
1235+ The style name is case insensitive. GNU style is the default.
1236+
1237+ -v
1238+ --verbose
1239+ Print the names of the files being processed.
1240+
1241+ -V
1242+ --version
1243+ Print the version of pygettext and exit.
1244+
1245+ -w columns
1246+ --width=columns
1247+ Set width of output to columns.
1248+
1249+ -x filename
1250+ --exclude-file=filename
1251+ Specify a file that contains a list of strings that are not be
1252+ extracted from the input files. Each string to be excluded must
1253+ appear on a line by itself in the file.
1254+
1255+ -X filename
1256+ --no-docstrings=filename
1257+ Specify a file that contains a list of files (one per line) that
1258+ should not have their docstrings extracted. This is only useful in
1259+ conjunction with the -D option above.
1260+
1261+If `inputfile' is -, standard input is read.
1262+"""
1263+
1264+import os
1265+import sys
1266+import time
1267+import getopt
1268+import tokenize
1269+import operator
1270+
1271+# for selftesting
1272+try:
1273+ import fintl
1274+ _ = fintl.gettext
1275+except ImportError:
1276+ def _(s): return s
1277+
1278+__version__ = '1.4'
1279+
1280+default_keywords = ['_']
1281+DEFAULTKEYWORDS = ', '.join(default_keywords)
1282+
1283+EMPTYSTRING = ''
1284+
1285+
1286+
1287
1288+# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
1289+# there.
1290+pot_header = _('''\
1291+# SOME DESCRIPTIVE TITLE.
1292+# Copyright (C) YEAR ORGANIZATION
1293+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
1294+#
1295+msgid ""
1296+msgstr ""
1297+"Project-Id-Version: PACKAGE VERSION\\n"
1298+"POT-Creation-Date: %(time)s\\n"
1299+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
1300+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
1301+"Language-Team: LANGUAGE <LL@li.org>\\n"
1302+"MIME-Version: 1.0\\n"
1303+"Content-Type: text/plain; charset=CHARSET\\n"
1304+"Content-Transfer-Encoding: ENCODING\\n"
1305+"Generated-By: pygettext.py %(version)s\\n"
1306+
1307+''')
1308+
1309+
1310
1311+def usage(code, msg=''):
1312+ if code:
1313+ fd = sys.stderr
1314+ else:
1315+ fd = sys.stdout
1316+ print >> fd, _(__doc__) % globals()
1317+ if msg:
1318+ print >> fd, msg
1319+ sys.exit(code)
1320+
1321+
1322+
1323
1324+escapes = []
1325+
1326+def make_escapes(pass_iso8859):
1327+ global escapes
1328+ if pass_iso8859:
1329+ # Allow iso-8859 characters to pass through so that e.g. 'msgid
1330+ # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'.
1331+ # Otherwise we escape any character outside the 32..126 range.
1332+ mod = 128
1333+ else:
1334+ mod = 256
1335+ for i in range(256):
1336+ if 32 <= (i % mod) <= 126:
1337+ escapes.append(chr(i))
1338+ else:
1339+ escapes.append("\\%03o" % i)
1340+ escapes[ord('\\')] = '\\\\'
1341+ escapes[ord('\t')] = '\\t'
1342+ escapes[ord('\r')] = '\\r'
1343+ escapes[ord('\n')] = '\\n'
1344+ escapes[ord('\"')] = '\\"'
1345+
1346+
1347+def escape(s):
1348+ global escapes
1349+ s = list(s)
1350+ for i in range(len(s)):
1351+ s[i] = escapes[ord(s[i])]
1352+ return EMPTYSTRING.join(s)
1353+
1354+
1355+def safe_eval(s):
1356+ # unwrap quotes, safely
1357+ return eval(s, {'__builtins__':{}}, {})
1358+
1359+
1360+def normalize(s):
1361+ # This converts the various Python string types into a format that is
1362+ # appropriate for .po files, namely much closer to C style.
1363+ lines = s.split('\n')
1364+ if len(lines) == 1:
1365+ s = '"' + escape(s) + '"'
1366+ else:
1367+ if not lines[-1]:
1368+ del lines[-1]
1369+ lines[-1] = lines[-1] + '\n'
1370+ for i in range(len(lines)):
1371+ lines[i] = escape(lines[i])
1372+ lineterm = '\\n"\n"'
1373+ s = '""\n"' + lineterm.join(lines) + '"'
1374+ return s
1375+
1376+
1377+
1378
1379+class TokenEater:
1380+ def __init__(self, options):
1381+ self.__options = options
1382+ self.__messages = {}
1383+ self.__state = self.__waiting
1384+ self.__data = []
1385+ self.__lineno = -1
1386+ self.__freshmodule = 1
1387+ self.__curfile = None
1388+
1389+ def __call__(self, ttype, tstring, stup, etup, line):
1390+ # dispatch
1391+## import token
1392+## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \
1393+## 'tstring:', tstring
1394+ self.__state(ttype, tstring, stup[0])
1395+
1396+ def __waiting(self, ttype, tstring, lineno):
1397+ opts = self.__options
1398+ # Do docstring extractions, if enabled
1399+ if opts.docstrings and not opts.nodocstrings.get(self.__curfile):
1400+ # module docstring?
1401+ if self.__freshmodule:
1402+ if ttype == tokenize.STRING:
1403+ self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
1404+ self.__freshmodule = 0
1405+ elif ttype not in (tokenize.COMMENT, tokenize.NL):
1406+ self.__freshmodule = 0
1407+ return
1408+ # class docstring?
1409+ if ttype == tokenize.NAME and tstring in ('class', 'def'):
1410+ self.__state = self.__suiteseen
1411+ return
1412+ if ttype == tokenize.NAME and tstring in opts.keywords:
1413+ self.__state = self.__keywordseen
1414+
1415+ def __suiteseen(self, ttype, tstring, lineno):
1416+ # ignore anything until we see the colon
1417+ if ttype == tokenize.OP and tstring == ':':
1418+ self.__state = self.__suitedocstring
1419+
1420+ def __suitedocstring(self, ttype, tstring, lineno):
1421+ # ignore any intervening noise
1422+ if ttype == tokenize.STRING:
1423+ self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
1424+ self.__state = self.__waiting
1425+ elif ttype not in (tokenize.NEWLINE, tokenize.INDENT,
1426+ tokenize.COMMENT):
1427+ # there was no class docstring
1428+ self.__state = self.__waiting
1429+
1430+ def __keywordseen(self, ttype, tstring, lineno):
1431+ if ttype == tokenize.OP and tstring == '(':
1432+ self.__data = []
1433+ self.__lineno = lineno
1434+ self.__state = self.__openseen
1435+ else:
1436+ self.__state = self.__waiting
1437+
1438+ def __openseen(self, ttype, tstring, lineno):
1439+ if ttype == tokenize.OP and tstring == ')':
1440+ # We've seen the last of the translatable strings. Record the
1441+ # line number of the first line of the strings and update the list
1442+ # of messages seen. Reset state for the next batch. If there
1443+ # were no strings inside _(), then just ignore this entry.
1444+ if self.__data:
1445+ self.__addentry(EMPTYSTRING.join(self.__data))
1446+ self.__state = self.__waiting
1447+ elif ttype == tokenize.STRING:
1448+ self.__data.append(safe_eval(tstring))
1449+ # TBD: should we warn if we seen anything else?
1450+
1451+ def __addentry(self, msg, lineno=None, isdocstring=0):
1452+ if lineno is None:
1453+ lineno = self.__lineno
1454+ if not msg in self.__options.toexclude:
1455+ entry = (self.__curfile, lineno)
1456+ self.__messages.setdefault(msg, {})[entry] = isdocstring
1457+
1458+ def set_filename(self, filename):
1459+ self.__curfile = filename
1460+ self.__freshmodule = 1
1461+
1462+ def write(self, fp):
1463+ options = self.__options
1464+ timestamp = time.ctime(time.time())
1465+ # The time stamp in the header doesn't have the same format as that
1466+ # generated by xgettext...
1467+ print >> fp, pot_header % {'time': timestamp, 'version': __version__}
1468+ # Sort the entries. First sort each particular entry's keys, then
1469+ # sort all the entries by their first item.
1470+ reverse = {}
1471+ for k, v in self.__messages.items():
1472+ keys = v.keys()
1473+ keys.sort()
1474+ reverse.setdefault(tuple(keys), []).append((k, v))
1475+ rkeys = reverse.keys()
1476+ rkeys.sort()
1477+ for rkey in rkeys:
1478+ rentries = reverse[rkey]
1479+ rentries.sort()
1480+ for k, v in rentries:
1481+ isdocstring = 0
1482+ # If the entry was gleaned out of a docstring, then add a
1483+ # comment stating so. This is to aid translators who may wish
1484+ # to skip translating some unimportant docstrings.
1485+ if reduce(operator.__add__, v.values()):
1486+ isdocstring = 1
1487+ # k is the message string, v is a dictionary-set of (filename,
1488+ # lineno) tuples. We want to sort the entries in v first by
1489+ # file name and then by line number.
1490+ v = v.keys()
1491+ v.sort()
1492+ if not options.writelocations:
1493+ pass
1494+ # location comments are different b/w Solaris and GNU:
1495+ elif options.locationstyle == options.SOLARIS:
1496+ for filename, lineno in v:
1497+ d = {'filename': filename, 'lineno': lineno}
1498+ print >>fp, _(
1499+ '# File: %(filename)s, line: %(lineno)d') % d
1500+ elif options.locationstyle == options.GNU:
1501+ # fit as many locations on one line, as long as the
1502+ # resulting line length doesn't exceeds 'options.width'
1503+ locline = '#:'
1504+ for filename, lineno in v:
1505+ d = {'filename': filename, 'lineno': lineno}
1506+ s = _(' %(filename)s:%(lineno)d') % d
1507+ if len(locline) + len(s) <= options.width:
1508+ locline = locline + s
1509+ else:
1510+ print >> fp, locline
1511+ locline = "#:" + s
1512+ if len(locline) > 2:
1513+ print >> fp, locline
1514+ if isdocstring:
1515+ print >> fp, '#, docstring'
1516+ print >> fp, 'msgid', normalize(k)
1517+ print >> fp, 'msgstr ""\n'
1518+
1519+
1520+
1521
1522+def main():
1523+ global default_keywords
1524+ try:
1525+ opts, args = getopt.getopt(
1526+ sys.argv[1:],
1527+ 'ad:DEhk:Kno:p:S:Vvw:x:X:',
1528+ ['extract-all', 'default-domain=', 'escape', 'help',
1529+ 'keyword=', 'no-default-keywords',
1530+ 'add-location', 'no-location', 'output=', 'output-dir=',
1531+ 'style=', 'verbose', 'version', 'width=', 'exclude-file=',
1532+ 'docstrings', 'no-docstrings',
1533+ ])
1534+ except getopt.error, msg:
1535+ usage(1, msg)
1536+
1537+ # for holding option values
1538+ class Options:
1539+ # constants
1540+ GNU = 1
1541+ SOLARIS = 2
1542+ # defaults
1543+ extractall = 0 # FIXME: currently this option has no effect at all.
1544+ escape = 0
1545+ keywords = []
1546+ outpath = ''
1547+ outfile = 'messages.pot'
1548+ writelocations = 1
1549+ locationstyle = GNU
1550+ verbose = 0
1551+ width = 78
1552+ excludefilename = ''
1553+ docstrings = 0
1554+ nodocstrings = {}
1555+
1556+ options = Options()
1557+ locations = {'gnu' : options.GNU,
1558+ 'solaris' : options.SOLARIS,
1559+ }
1560+
1561+ # parse options
1562+ for opt, arg in opts:
1563+ if opt in ('-h', '--help'):
1564+ usage(0)
1565+ elif opt in ('-a', '--extract-all'):
1566+ options.extractall = 1
1567+ elif opt in ('-d', '--default-domain'):
1568+ options.outfile = arg + '.pot'
1569+ elif opt in ('-E', '--escape'):
1570+ options.escape = 1
1571+ elif opt in ('-D', '--docstrings'):
1572+ options.docstrings = 1
1573+ elif opt in ('-k', '--keyword'):
1574+ options.keywords.append(arg)
1575+ elif opt in ('-K', '--no-default-keywords'):
1576+ default_keywords = []
1577+ elif opt in ('-n', '--add-location'):
1578+ options.writelocations = 1
1579+ elif opt in ('--no-location',):
1580+ options.writelocations = 0
1581+ elif opt in ('-S', '--style'):
1582+ options.locationstyle = locations.get(arg.lower())
1583+ if options.locationstyle is None:
1584+ usage(1, _('Invalid value for --style: %s') % arg)
1585+ elif opt in ('-o', '--output'):
1586+ options.outfile = arg
1587+ elif opt in ('-p', '--output-dir'):
1588+ options.outpath = arg
1589+ elif opt in ('-v', '--verbose'):
1590+ options.verbose = 1
1591+ elif opt in ('-V', '--version'):
1592+ print _('pygettext.py (xgettext for Python) %s') % __version__
1593+ sys.exit(0)
1594+ elif opt in ('-w', '--width'):
1595+ try:
1596+ options.width = int(arg)
1597+ except ValueError:
1598+ usage(1, _('--width argument must be an integer: %s') % arg)
1599+ elif opt in ('-x', '--exclude-file'):
1600+ options.excludefilename = arg
1601+ elif opt in ('-X', '--no-docstrings'):
1602+ fp = open(arg)
1603+ try:
1604+ while 1:
1605+ line = fp.readline()
1606+ if not line:
1607+ break
1608+ options.nodocstrings[line[:-1]] = 1
1609+ finally:
1610+ fp.close()
1611+
1612+ # calculate escapes
1613+ make_escapes(options.escape)
1614+
1615+ # calculate all keywords
1616+ options.keywords.extend(default_keywords)
1617+
1618+ # initialize list of strings to exclude
1619+ if options.excludefilename:
1620+ try:
1621+ fp = open(options.excludefilename)
1622+ options.toexclude = fp.readlines()
1623+ fp.close()
1624+ except IOError:
1625+ print >> sys.stderr, _(
1626+ "Can't read --exclude-file: %s") % options.excludefilename
1627+ sys.exit(1)
1628+ else:
1629+ options.toexclude = []
1630+
1631+ # slurp through all the files
1632+ eater = TokenEater(options)
1633+ for filename in args:
1634+ if filename == '-':
1635+ if options.verbose:
1636+ print _('Reading standard input')
1637+ fp = sys.stdin
1638+ closep = 0
1639+ else:
1640+ if options.verbose:
1641+ print _('Working on %s') % filename
1642+ fp = open(filename)
1643+ closep = 1
1644+ try:
1645+ eater.set_filename(filename)
1646+ try:
1647+ tokenize.tokenize(fp.readline, eater)
1648+ except tokenize.TokenError, e:
1649+ print >> sys.stderr, '%s: %s, line %d, column %d' % (
1650+ e[0], filename, e[1][0], e[1][1])
1651+ finally:
1652+ if closep:
1653+ fp.close()
1654+
1655+ # write the output
1656+ if options.outfile == '-':
1657+ fp = sys.stdout
1658+ closep = 0
1659+ else:
1660+ if options.outpath:
1661+ options.outfile = os.path.join(options.outpath, options.outfile)
1662+ fp = open(options.outfile, 'w')
1663+ closep = 1
1664+ try:
1665+ eater.write(fp)
1666+ finally:
1667+ if closep:
1668+ fp.close()
1669+
1670+
1671
1672+if __name__ == '__main__':
1673+ main()
1674+ # some more test strings
1675+ _(u'a unicode string')
1676
1677=== added file 'bin/remove_members'
1678--- bin/remove_members 1970-01-01 00:00:00 +0000
1679+++ bin/remove_members 2010-10-14 12:15:59 +0000
1680@@ -0,0 +1,186 @@
1681+#! @PYTHON@
1682+#
1683+# Copyright (C) 1998-2005 by the Free Software Foundation, Inc.
1684+#
1685+# This program is free software; you can redistribute it and/or
1686+# modify it under the terms of the GNU General Public License
1687+# as published by the Free Software Foundation; either version 2
1688+# of the License, or (at your option) any later version.
1689+#
1690+# This program is distributed in the hope that it will be useful,
1691+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1692+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1693+# GNU General Public License for more details.
1694+#
1695+# You should have received a copy of the GNU General Public License
1696+# along with this program; if not, write to the Free Software
1697+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
1698+# USA.
1699+
1700+"""Remove members from a list.
1701+
1702+Usage:
1703+ remove_members [options] [listname] [addr1 ...]
1704+
1705+Options:
1706+
1707+ --file=file
1708+ -f file
1709+ Remove member addresses found in the given file. If file is
1710+ `-', read stdin.
1711+
1712+ --all
1713+ -a
1714+ Remove all members of the mailing list.
1715+ (mutually exclusive with --fromall)
1716+
1717+ --fromall
1718+ Removes the given addresses from all the lists on this system
1719+ regardless of virtual domains if you have any. This option cannot be
1720+ used -a/--all. Also, you should not specify a listname when using
1721+ this option.
1722+
1723+ --nouserack
1724+ -n
1725+ Don't send the user acknowledgements. If not specified, the list
1726+ default value is used.
1727+
1728+ --noadminack
1729+ -N
1730+ Don't send the admin acknowledgements. If not specified, the list
1731+ default value is used.
1732+
1733+ --help
1734+ -h
1735+ Print this help message and exit.
1736+
1737+ listname is the name of the mailing list to use.
1738+
1739+ addr1 ... are additional addresses to remove.
1740+"""
1741+
1742+import sys
1743+import getopt
1744+
1745+import paths
1746+from Mailman import MailList
1747+from Mailman import Utils
1748+from Mailman import Errors
1749+from Mailman.i18n import _
1750+
1751+try:
1752+ True, False
1753+except NameError:
1754+ True = 1
1755+ False = 0
1756+
1757+
1758+
1759
1760+def usage(code, msg=''):
1761+ if code:
1762+ fd = sys.stderr
1763+ else:
1764+ fd = sys.stdout
1765+ print >> fd, _(__doc__)
1766+ if msg:
1767+ print >> fd, msg
1768+ sys.exit(code)
1769+
1770+
1771+def ReadFile(filename):
1772+ lines = []
1773+ if filename == "-":
1774+ fp = sys.stdin
1775+ closep = False
1776+ else:
1777+ fp = open(filename)
1778+ closep = True
1779+ lines = filter(None, [line.strip() for line in fp.readlines()])
1780+ if closep:
1781+ fp.close()
1782+ return lines
1783+
1784+
1785+
1786
1787+def main():
1788+ try:
1789+ opts, args = getopt.getopt(
1790+ sys.argv[1:], 'naf:hN',
1791+ ['all', 'fromall', 'file=', 'help', 'nouserack', 'noadminack'])
1792+ except getopt.error, msg:
1793+ usage(1, msg)
1794+
1795+ filename = None
1796+ all = False
1797+ alllists = False
1798+ # None means use list default
1799+ userack = None
1800+ admin_notif = None
1801+
1802+ for opt, arg in opts:
1803+ if opt in ('-h', '--help'):
1804+ usage(0)
1805+ elif opt in ('-f', '--file'):
1806+ filename = arg
1807+ elif opt in ('-a', '--all'):
1808+ all = True
1809+ elif opt == '--fromall':
1810+ alllists = True
1811+ elif opt in ('-n', '--nouserack'):
1812+ userack = False
1813+ elif opt in ('-N', '--noadminack'):
1814+ admin_notif = False
1815+
1816+ if len(args) < 1 and not (filename and alllists):
1817+ usage(1)
1818+
1819+ # You probably don't want to delete all the users of all the lists -- Marc
1820+ if all and alllists:
1821+ usage(1)
1822+
1823+ if alllists:
1824+ addresses = args
1825+ else:
1826+ listname = args[0].lower().strip()
1827+ addresses = args[1:]
1828+
1829+ if alllists:
1830+ listnames = Utils.list_names()
1831+ else:
1832+ listnames = [listname]
1833+
1834+ if filename:
1835+ try:
1836+ addresses = addresses + ReadFile(filename)
1837+ except IOError:
1838+ print _('Could not open file for reading: %(filename)s.')
1839+
1840+ for listname in listnames:
1841+ try:
1842+ # open locked
1843+ mlist = MailList.MailList(listname)
1844+ except Errors.MMListError:
1845+ print _('Error opening list %(listname)s... skipping.')
1846+ continue
1847+
1848+ if all:
1849+ addresses = mlist.getMembers()
1850+
1851+ try:
1852+ for addr in addresses:
1853+ if not mlist.isMember(addr):
1854+ if not alllists:
1855+ print _('No such member: %(addr)s')
1856+ continue
1857+ mlist.ApprovedDeleteMember(addr, 'bin/remove_members',
1858+ admin_notif, userack)
1859+ if alllists:
1860+ print _("User `%(addr)s' removed from list: %(listname)s.")
1861+ mlist.Save()
1862+ finally:
1863+ mlist.Unlock()
1864+
1865+
1866+
1867
1868+if __name__ == '__main__':
1869+ main()
1870
1871=== added file 'bin/reset_pw.py'
1872--- bin/reset_pw.py 1970-01-01 00:00:00 +0000
1873+++ bin/reset_pw.py 2010-10-14 12:15:59 +0000
1874@@ -0,0 +1,83 @@
1875+#! @PYTHON@
1876+#
1877+# Copyright (C) 2004-2007 by the Free Software Foundation, Inc.
1878+#
1879+# This program is free software; you can redistribute it and/or
1880+# modify it under the terms of the GNU General Public License
1881+# as published by the Free Software Foundation; either version 2
1882+# of the License, or (at your option) any later version.
1883+#
1884+# This program is distributed in the hope that it will be useful,
1885+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1886+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1887+# GNU General Public License for more details.
1888+#
1889+# You should have received a copy of the GNU General Public License
1890+# along with this program; if not, write to the Free Software
1891+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1892+
1893+# Inspired by Florian Weimer.
1894+
1895+"""Reset the passwords for members of a mailing list.
1896+
1897+This script resets all the passwords of a mailing list's members. It can also
1898+be used to reset the lists of all members of all mailing lists, but it is your
1899+responsibility to let the users know that their passwords have been changed.
1900+
1901+This script is intended to be run as a bin/withlist script, i.e.
1902+
1903+% bin/withlist -l -r reset_pw listname [options]
1904+
1905+Options:
1906+ -v / --verbose
1907+ Print what the script is doing.
1908+"""
1909+
1910+import sys
1911+import getopt
1912+
1913+import paths
1914+from Mailman import Utils
1915+from Mailman.i18n import _
1916+
1917+
1918+
1919
1920+def usage(code, msg=''):
1921+ if code:
1922+ fd = sys.stderr
1923+ else:
1924+ fd = sys.stdout
1925+ print >> fd, _(__doc__.replace('%', '%%'))
1926+ if msg:
1927+ print >> fd, msg
1928+ sys.exit(code)
1929+
1930+
1931+
1932
1933+def reset_pw(mlist, *args):
1934+ try:
1935+ opts, args = getopt.getopt(args, 'v', ['verbose'])
1936+ except getopt.error, msg:
1937+ usage(1, msg)
1938+
1939+ verbose = False
1940+ for opt, args in opts:
1941+ if opt in ('-v', '--verbose'):
1942+ verbose = True
1943+
1944+ listname = mlist.internal_name()
1945+ if verbose:
1946+ print _('Changing passwords for list: %(listname)s')
1947+
1948+ for member in mlist.getMembers():
1949+ randompw = Utils.MakeRandomPassword()
1950+ mlist.setMemberPassword(member, randompw)
1951+ if verbose:
1952+ print _('New password for member %(member)40s: %(randompw)s')
1953+
1954+ mlist.Save()
1955+
1956+
1957+
1958
1959+if __name__ == '__main__':
1960+ usage(0)
1961
1962=== added file 'bin/sync_members'
1963--- bin/sync_members 1970-01-01 00:00:00 +0000
1964+++ bin/sync_members 2010-10-14 12:15:59 +0000
1965@@ -0,0 +1,286 @@
1966+#! @PYTHON@
1967+#
1968+# Copyright (C) 1998-2003 by the Free Software Foundation, Inc.
1969+#
1970+# This program is free software; you can redistribute it and/or
1971+# modify it under the terms of the GNU General Public License
1972+# as published by the Free Software Foundation; either version 2
1973+# of the License, or (at your option) any later version.
1974+#
1975+# This program is distributed in the hope that it will be useful,
1976+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1977+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1978+# GNU General Public License for more details.
1979+#
1980+# You should have received a copy of the GNU General Public License
1981+# along with this program; if not, write to the Free Software
1982+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1983+
1984+"""Synchronize a mailing list's membership with a flat file.
1985+
1986+This script is useful if you have a Mailman mailing list and a sendmail
1987+:include: style list of addresses (also as is used in Majordomo). For every
1988+address in the file that does not appear in the mailing list, the address is
1989+added. For every address in the mailing list that does not appear in the
1990+file, the address is removed. Other options control what happens when an
1991+address is added or removed.
1992+
1993+Usage: %(PROGRAM)s [options] -f file listname
1994+
1995+Where `options' are:
1996+
1997+ --no-change
1998+ -n
1999+ Don't actually make the changes. Instead, print out what would be
2000+ done to the list.
2001+
2002+ --welcome-msg[=<yes|no>]
2003+ -w[=<yes|no>]
2004+ Sets whether or not to send the newly added members a welcome
2005+ message, overriding whatever the list's `send_welcome_msg' setting
2006+ is. With -w=yes or -w, the welcome message is sent. With -w=no, no
2007+ message is sent.
2008+
2009+ --goodbye-msg[=<yes|no>]
2010+ -g[=<yes|no>]
2011+ Sets whether or not to send the goodbye message to removed members,
2012+ overriding whatever the list's `send_goodbye_msg' setting is. With
2013+ -g=yes or -g, the goodbye message is sent. With -g=no, no message is
2014+ sent.
2015+
2016+ --digest[=<yes|no>]
2017+ -d[=<yes|no>]
2018+ Selects whether to make newly added members receive messages in
2019+ digests. With -d=yes or -d, they become digest members. With -d=no
2020+ (or if no -d option given) they are added as regular members.
2021+
2022+ --notifyadmin[=<yes|no>]
2023+ -a[=<yes|no>]
2024+ Specifies whether the admin should be notified for each subscription
2025+ or unsubscription. If you're adding a lot of addresses, you
2026+ definitely want to turn this off! With -a=yes or -a, the admin is
2027+ notified. With -a=no, the admin is not notified. With no -a option,
2028+ the default for the list is used.
2029+
2030+ --file <filename | ->
2031+ -f <filename | ->
2032+ This option is required. It specifies the flat file to synchronize
2033+ against. Email addresses must appear one per line. If filename is
2034+ `-' then stdin is used.
2035+
2036+ --help
2037+ -h
2038+ Print this message.
2039+
2040+ listname
2041+ Required. This specifies the list to synchronize.
2042+"""
2043+
2044+import sys
2045+
2046+import paths
2047+# Import this /after/ paths so that the sys.path is properly hacked
2048+import email.Utils
2049+
2050+from Mailman import MailList
2051+from Mailman import Errors
2052+from Mailman import Utils
2053+from Mailman.UserDesc import UserDesc
2054+from Mailman.i18n import _
2055+
2056+
2057+
2058
2059+PROGRAM = sys.argv[0]
2060+
2061+def usage(code, msg=''):
2062+ if code:
2063+ fd = sys.stderr
2064+ else:
2065+ fd = sys.stdout
2066+ print >> fd, _(__doc__)
2067+ if msg:
2068+ print >> fd, msg
2069+ sys.exit(code)
2070+
2071+
2072+
2073
2074+def yesno(opt):
2075+ i = opt.find('=')
2076+ yesno = opt[i+1:].lower()
2077+ if yesno in ('y', 'yes'):
2078+ return 1
2079+ elif yesno in ('n', 'no'):
2080+ return 0
2081+ else:
2082+ usage(1, _('Bad choice: %(yesno)s'))
2083+ # no return
2084+
2085+
2086+def main():
2087+ dryrun = 0
2088+ digest = 0
2089+ welcome = None
2090+ goodbye = None
2091+ filename = None
2092+ listname = None
2093+ notifyadmin = None
2094+
2095+ # TBD: can't use getopt with this command line syntax, which is broken and
2096+ # should be changed to be getopt compatible.
2097+ i = 1
2098+ while i < len(sys.argv):
2099+ opt = sys.argv[i]
2100+ if opt in ('-h', '--help'):
2101+ usage(0)
2102+ elif opt in ('-n', '--no-change'):
2103+ dryrun = 1
2104+ i += 1
2105+ print _('Dry run mode')
2106+ elif opt in ('-d', '--digest'):
2107+ digest = 1
2108+ i += 1
2109+ elif opt.startswith('-d=') or opt.startswith('--digest='):
2110+ digest = yesno(opt)
2111+ i += 1
2112+ elif opt in ('-w', '--welcome-msg'):
2113+ welcome = 1
2114+ i += 1
2115+ elif opt.startswith('-w=') or opt.startswith('--welcome-msg='):
2116+ welcome = yesno(opt)
2117+ i += 1
2118+ elif opt in ('-g', '--goodbye-msg'):
2119+ goodbye = 1
2120+ i += 1
2121+ elif opt.startswith('-g=') or opt.startswith('--goodbye-msg='):
2122+ goodbye = yesno(opt)
2123+ i += 1
2124+ elif opt in ('-f', '--file'):
2125+ if filename is not None:
2126+ usage(1, _('Only one -f switch allowed'))
2127+ try:
2128+ filename = sys.argv[i+1]
2129+ except IndexError:
2130+ usage(1, _('No argument to -f given'))
2131+ i += 2
2132+ elif opt in ('-a', '--notifyadmin'):
2133+ notifyadmin = 1
2134+ i += 1
2135+ elif opt.startswith('-a=') or opt.startswith('--notifyadmin='):
2136+ notifyadmin = yesno(opt)
2137+ i += 1
2138+ elif opt[0] == '-':
2139+ usage(1, _('Illegal option: %(opt)s'))
2140+ else:
2141+ try:
2142+ listname = sys.argv[i].lower()
2143+ i += 1
2144+ except IndexError:
2145+ usage(1, _('No listname given'))
2146+ break
2147+
2148+ if listname is None or filename is None:
2149+ usage(1, _('Must have a listname and a filename'))
2150+
2151+ # read the list of addresses to sync to from the file
2152+ if filename == '-':
2153+ filemembers = sys.stdin.readlines()
2154+ else:
2155+ try:
2156+ fp = open(filename)
2157+ except IOError, (code, msg):
2158+ usage(1, _('Cannot read address file: %(filename)s: %(msg)s'))
2159+ try:
2160+ filemembers = fp.readlines()
2161+ finally:
2162+ fp.close()
2163+
2164+ # strip out lines we don't care about, they are comments (# in first
2165+ # non-whitespace) or are blank
2166+ for i in range(len(filemembers)-1, -1, -1):
2167+ addr = filemembers[i].strip()
2168+ if addr == '' or addr[:1] == '#':
2169+ del filemembers[i]
2170+ print _('Ignore : %(addr)30s')
2171+
2172+ # first filter out any invalid addresses
2173+ filemembers = email.Utils.getaddresses(filemembers)
2174+ invalid = 0
2175+ for name, addr in filemembers:
2176+ try:
2177+ Utils.ValidateEmail(addr)
2178+ except Errors.EmailAddressError:
2179+ print _('Invalid : %(addr)30s')
2180+ invalid = 1
2181+ if invalid:
2182+ print _('You must fix the preceding invalid addresses first.')
2183+ sys.exit(1)
2184+
2185+ # get the locked list object
2186+ try:
2187+ mlist = MailList.MailList(listname)
2188+ except Errors.MMListError, e:
2189+ print _('No such list: %(listname)s')
2190+ sys.exit(1)
2191+
2192+ try:
2193+ # Get the list of addresses currently subscribed
2194+ addrs = {}
2195+ needsadding = {}
2196+ matches = {}
2197+ for addr in mlist.getMemberCPAddresses(mlist.getMembers()):
2198+ addrs[addr.lower()] = addr
2199+
2200+ for name, addr in filemembers:
2201+ # Any address found in the file that is also in the list can be
2202+ # ignored. If not found in the list, it must be added later.
2203+ laddr = addr.lower()
2204+ if addrs.has_key(laddr):
2205+ del addrs[laddr]
2206+ matches[laddr] = 1
2207+ elif not matches.has_key(laddr):
2208+ needsadding[laddr] = (name, addr)
2209+
2210+ if not needsadding and not addrs:
2211+ print _('Nothing to do.')
2212+ sys.exit(0)
2213+
2214+ enc = sys.getdefaultencoding()
2215+ # addrs contains now all the addresses that need removing
2216+ for laddr, (name, addr) in needsadding.items():
2217+ pw = Utils.MakeRandomPassword()
2218+ # should not already be subscribed, otherwise our test above is
2219+ # broken. Bogosity is if the address is listed in the file more
2220+ # than once. Second and subsequent ones trigger an
2221+ # MMAlreadyAMember error. Just catch it and go on.
2222+ userdesc = UserDesc(addr, name, pw, digest)
2223+ try:
2224+ if not dryrun:
2225+ mlist.ApprovedAddMember(userdesc, welcome, notifyadmin)
2226+ s = email.Utils.formataddr((name, addr)).encode(enc, 'replace')
2227+ print _('Added : %(s)s')
2228+ except Errors.MMAlreadyAMember:
2229+ pass
2230+
2231+ for laddr, addr in addrs.items():
2232+ # Should be a member, otherwise our test above is broken
2233+ name = mlist.getMemberName(laddr) or ''
2234+ if not dryrun:
2235+ try:
2236+ mlist.ApprovedDeleteMember(addr, admin_notif=notifyadmin,
2237+ userack=goodbye)
2238+ except Errors.NotAMemberError:
2239+ # This can happen if the address is illegal (i.e. can't be
2240+ # parsed by email.Utils.parseaddr()) but for legacy
2241+ # reasons is in the database. Use a lower level remove to
2242+ # get rid of this member's entry
2243+ mlist.removeMember(addr)
2244+ s = email.Utils.formataddr((name, addr)).encode(enc, 'replace')
2245+ print _('Removed: %(s)s')
2246+
2247+ mlist.Save()
2248+ finally:
2249+ mlist.Unlock()
2250+
2251+
2252+if __name__ == '__main__':
2253+ main()
2254
2255=== added file 'bin/templ2pot.py'
2256--- bin/templ2pot.py 1970-01-01 00:00:00 +0000
2257+++ bin/templ2pot.py 2010-10-14 12:15:59 +0000
2258@@ -0,0 +1,120 @@
2259+#! @PYTHON@
2260+# Code stolen from pygettext.py
2261+# by Tokio Kikuchi <tkikuchi@is.kochi-u.ac.jp>
2262+
2263+"""templ2pot.py -- convert mailman template (en) to pot format.
2264+
2265+Usage: templ2pot.py inputfile ...
2266+
2267+Options:
2268+
2269+ -h, --help
2270+
2271+Inputfiles are english templates. Outputs are written to stdout.
2272+"""
2273+
2274+import sys
2275+import getopt
2276+
2277+
2278+
2279
2280+try:
2281+ import paths
2282+ from Mailman.i18n import _
2283+except ImportError:
2284+ def _(s): return s
2285+
2286+EMPTYSTRING = ''
2287+
2288+
2289+
2290
2291+def usage(code, msg=''):
2292+ if code:
2293+ fd = sys.stderr
2294+ else:
2295+ fd = sys.stdout
2296+ print >> fd, _(__doc__) % globals()
2297+ if msg:
2298+ print >> fd, msg
2299+ sys.exit(code)
2300+
2301+
2302+
2303
2304+escapes = []
2305+
2306+def make_escapes(pass_iso8859):
2307+ global escapes
2308+ if pass_iso8859:
2309+ # Allow iso-8859 characters to pass through so that e.g. 'msgid
2310+ # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'.
2311+ # Otherwise we escape any character outside the 32..126 range.
2312+ mod = 128
2313+ else:
2314+ mod = 256
2315+ for i in range(256):
2316+ if 32 <= (i % mod) <= 126:
2317+ escapes.append(chr(i))
2318+ else:
2319+ escapes.append("\\%03o" % i)
2320+ escapes[ord('\\')] = '\\\\'
2321+ escapes[ord('\t')] = '\\t'
2322+ escapes[ord('\r')] = '\\r'
2323+ escapes[ord('\n')] = '\\n'
2324+ escapes[ord('\"')] = '\\"'
2325+
2326+
2327+def escape(s):
2328+ global escapes
2329+ s = list(s)
2330+ for i in range(len(s)):
2331+ s[i] = escapes[ord(s[i])]
2332+ return EMPTYSTRING.join(s)
2333+
2334+
2335+def normalize(s):
2336+ # This converts the various Python string types into a format that is
2337+ # appropriate for .po files, namely much closer to C style.
2338+ lines = s.splitlines()
2339+ if len(lines) == 1:
2340+ s = '"' + escape(s) + '"'
2341+ else:
2342+ if not lines[-1]:
2343+ del lines[-1]
2344+ lines[-1] = lines[-1] + '\n'
2345+ for i in range(len(lines)):
2346+ lines[i] = escape(lines[i])
2347+ lineterm = '\\n"\n"'
2348+ s = '""\n"' + lineterm.join(lines) + '"'
2349+ return s
2350+
2351+
2352+
2353
2354+def main():
2355+ try:
2356+ opts, args = getopt.getopt(
2357+ sys.argv[1:],
2358+ 'h',
2359+ ['help',]
2360+ )
2361+ except getopt.error, msg:
2362+ usage(1, msg)
2363+
2364+ # parse options
2365+ for opt, arg in opts:
2366+ if opt in ('-h', '--help'):
2367+ usage(0)
2368+
2369+ # calculate escapes
2370+ make_escapes(0)
2371+
2372+ for filename in args:
2373+ print '#: %s:1' % filename
2374+ s = file(filename).read()
2375+ print '#, template'
2376+ print 'msgid', normalize(s)
2377+ print 'msgstr ""\n'
2378+
2379+
2380+
2381
2382+if __name__ == '__main__':
2383+ main()
2384
2385=== added file 'bin/transcheck'
2386--- bin/transcheck 1970-01-01 00:00:00 +0000
2387+++ bin/transcheck 2010-10-14 12:15:59 +0000
2388@@ -0,0 +1,412 @@
2389+#! @PYTHON@
2390+#
2391+# transcheck - (c) 2002 by Simone Piunno <pioppo@ferrara.linux.it>
2392+#
2393+# This program is free software; you can redistribute it and/or modify it
2394+# under the terms of the version 2.0 of the GNU General Public License as
2395+# published by the Free Software Foundation.
2396+#
2397+# This program is distributed in the hope that it will be useful, but
2398+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
2399+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
2400+# for more details.
2401+#
2402+# You should have received a copy of the GNU General Public License along
2403+# with this program; if not, write to the Free Software Foundation, Inc.,
2404+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2405+
2406+"""
2407+Check a given Mailman translation, making sure that variables and
2408+tags referenced in translation are the same variables and tags in
2409+the original templates and catalog.
2410+
2411+Usage:
2412+
2413+cd $MAILMAN_DIR
2414+%(program)s [-q] <lang>
2415+
2416+Where <lang> is your country code (e.g. 'it' for Italy) and -q is
2417+to ask for a brief summary.
2418+"""
2419+
2420+import sys
2421+import re
2422+import os
2423+import getopt
2424+
2425+import paths
2426+from Mailman.i18n import _
2427+
2428+program = sys.argv[0]
2429+
2430+
2431+
2432
2433+def usage(code, msg=''):
2434+ if code:
2435+ fd = sys.stderr
2436+ else:
2437+ fd = sys.stdout
2438+ print >> fd, _(__doc__)
2439+ if msg:
2440+ print >> fd, msg
2441+ sys.exit(code)
2442+
2443+
2444+
2445
2446+class TransChecker:
2447+ "check a translation comparing with the original string"
2448+ def __init__(self, regexp, escaped=None):
2449+ self.dict = {}
2450+ self.errs = []
2451+ self.regexp = re.compile(regexp)
2452+ self.escaped = None
2453+ if escaped:
2454+ self.escaped = re.compile(escaped)
2455+
2456+ def checkin(self, string):
2457+ "scan a string from the original file"
2458+ for key in self.regexp.findall(string):
2459+ if self.escaped and self.escaped.match(key):
2460+ continue
2461+ if self.dict.has_key(key):
2462+ self.dict[key] += 1
2463+ else:
2464+ self.dict[key] = 1
2465+
2466+ def checkout(self, string):
2467+ "scan a translated string"
2468+ for key in self.regexp.findall(string):
2469+ if self.escaped and self.escaped.match(key):
2470+ continue
2471+ if self.dict.has_key(key):
2472+ self.dict[key] -= 1
2473+ else:
2474+ self.errs.append(
2475+ "%(key)s was not found" %
2476+ { 'key' : key }
2477+ )
2478+
2479+ def computeErrors(self):
2480+ "check for differences between checked in and checked out"
2481+ for key in self.dict.keys():
2482+ if self.dict[key] < 0:
2483+ self.errs.append(
2484+ "Too much %(key)s" %
2485+ { 'key' : key }
2486+ )
2487+ if self.dict[key] > 0:
2488+ self.errs.append(
2489+ "Too few %(key)s" %
2490+ { 'key' : key }
2491+ )
2492+ return self.errs
2493+
2494+ def status(self):
2495+ if self.errs:
2496+ return "FAILED"
2497+ else:
2498+ return "OK"
2499+
2500+ def errorsAsString(self):
2501+ msg = ""
2502+ for err in self.errs:
2503+ msg += " - %(err)s" % { 'err': err }
2504+ return msg
2505+
2506+ def reset(self):
2507+ self.dict = {}
2508+ self.errs = []
2509+
2510+
2511+
2512
2513+class POParser:
2514+ "parse a .po file extracting msgids and msgstrs"
2515+ def __init__(self, filename=""):
2516+ self.status = 0
2517+ self.files = []
2518+ self.msgid = ""
2519+ self.msgstr = ""
2520+ self.line = 1
2521+ self.f = None
2522+ self.esc = { "n": "\n", "r": "\r", "t": "\t" }
2523+ if filename:
2524+ self.f = open(filename)
2525+
2526+ def open(self, filename):
2527+ self.f = open(filename)
2528+
2529+ def close(self):
2530+ self.f.close()
2531+
2532+ def parse(self):
2533+ """States table for the finite-states-machine parser:
2534+ 0 idle
2535+ 1 filename-or-comment
2536+ 2 msgid
2537+ 3 msgstr
2538+ 4 end
2539+ """
2540+ # each time we can safely re-initialize those vars
2541+ self.files = []
2542+ self.msgid = ""
2543+ self.msgstr = ""
2544+
2545+
2546+ # can't continue if status == 4, this is a dead status
2547+ if self.status == 4:
2548+ return 0
2549+
2550+ while 1:
2551+ # continue scanning, char-by-char
2552+ c = self.f.read(1)
2553+ if not c:
2554+ # EOF -> maybe we have a msgstr to save?
2555+ self.status = 4
2556+ if self.msgstr:
2557+ return 1
2558+ else:
2559+ return 0
2560+
2561+ # keep the line count up-to-date
2562+ if c == "\n":
2563+ self.line += 1
2564+
2565+ # a pound was detected the previous char...
2566+ if self.status == 1:
2567+ if c == ":":
2568+ # was a line of filenames
2569+ row = self.f.readline()
2570+ self.files += row.split()
2571+ self.line += 1
2572+ elif c == "\n":
2573+ # was a single pount on the line
2574+ pass
2575+ else:
2576+ # was a comment... discard
2577+ self.f.readline()
2578+ self.line += 1
2579+ # in every case, we switch to idle status
2580+ self.status = 0;
2581+ continue
2582+
2583+ # in idle status we search for a '#' or for a 'm'
2584+ if self.status == 0:
2585+ if c == "#":
2586+ # this could be a comment or a filename
2587+ self.status = 1;
2588+ continue
2589+ elif c == "m":
2590+ # this should be a msgid start...
2591+ s = self.f.read(4)
2592+ assert s == "sgid"
2593+ # so now we search for a '"'
2594+ self.status = 2
2595+ continue
2596+ # in idle only those other chars are possibile
2597+ assert c in [ "\n", " ", "\t" ]
2598+
2599+ # searching for the msgid string
2600+ if self.status == 2:
2601+ if c == "\n":
2602+ # a double LF is not possible here
2603+ c = self.f.read(1)
2604+ assert c != "\n"
2605+ if c == "\"":
2606+ # ok, this is the start of the string,
2607+ # now search for the end
2608+ while 1:
2609+ c = self.f.read(1)
2610+ if not c:
2611+ # EOF, bailout
2612+ self.status = 4
2613+ return 0
2614+ if c == "\\":
2615+ # a quoted char...
2616+ c = self.f.read(1)
2617+ if self.esc.has_key(c):
2618+ self.msgid += self.esc[c]
2619+ else:
2620+ self.msgid += c
2621+ continue
2622+ if c == "\"":
2623+ # end of string found
2624+ break
2625+ # a normal char, add it
2626+ self.msgid += c
2627+ if c == "m":
2628+ # this should be a msgstr identifier
2629+ s = self.f.read(5)
2630+ assert s == "sgstr"
2631+ # ok, now search for the msgstr string
2632+ self.status = 3
2633+
2634+ # searching for the msgstr string
2635+ if self.status == 3:
2636+ if c == "\n":
2637+ # a double LF is the end of the msgstr!
2638+ c = self.f.read(1)
2639+ if c == "\n":
2640+ # ok, time to go idle and return
2641+ self.status = 0
2642+ self.line += 1
2643+ return 1
2644+ if c == "\"":
2645+ # start of string found
2646+ while 1:
2647+ c = self.f.read(1)
2648+ if not c:
2649+ # EOF, bail out
2650+ self.status = 4
2651+ return 1
2652+ if c == "\\":
2653+ # a quoted char...
2654+ c = self.f.read(1)
2655+ if self.esc.has_key(c):
2656+ self.msgid += self.esc[c]
2657+ else:
2658+ self.msgid += c
2659+ continue
2660+ if c == "\"":
2661+ # end of string
2662+ break
2663+ # a normal char, add it
2664+ self.msgstr += c
2665+
2666+
2667+
2668+
2669
2670+def check_file(translatedFile, originalFile, html=0, quiet=0):
2671+ """check a translated template against the original one
2672+ search also <MM-*> tags if html is not zero"""
2673+
2674+ if html:
2675+ c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd]|</?MM-[^>]+>)", "^%%$")
2676+ else:
2677+ c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd])", "^%%$")
2678+
2679+ try:
2680+ f = open(originalFile)
2681+ except IOError:
2682+ if not quiet:
2683+ print " - Can'open original file " + originalFile
2684+ return 1
2685+
2686+ while 1:
2687+ line = f.readline()
2688+ if not line: break
2689+ c.checkin(line)
2690+
2691+ f.close()
2692+
2693+ try:
2694+ f = open(translatedFile)
2695+ except IOError:
2696+ if not quiet:
2697+ print " - Can'open translated file " + translatedFile
2698+ return 1
2699+
2700+ while 1:
2701+ line = f.readline()
2702+ if not line: break
2703+ c.checkout(line)
2704+
2705+ f.close()
2706+
2707+ n = 0
2708+ msg = ""
2709+ for desc in c.computeErrors():
2710+ n +=1
2711+ if not quiet:
2712+ print " - %(desc)s" % { 'desc': desc }
2713+ return n
2714+
2715+
2716+
2717
2718+def check_po(file, quiet=0):
2719+ "scan the po file comparing msgids with msgstrs"
2720+ n = 0
2721+ p = POParser(file)
2722+ c = TransChecker("(%%|%\([^)]+\)[0-9]*[sdu]|%[0-9]*[sdu])", "^%%$")
2723+ while p.parse():
2724+ if p.msgstr:
2725+ c.reset()
2726+ c.checkin(p.msgid)
2727+ c.checkout(p.msgstr)
2728+ for desc in c.computeErrors():
2729+ n += 1
2730+ if not quiet:
2731+ print " - near line %(line)d %(file)s: %(desc)s" % {
2732+ 'line': p.line,
2733+ 'file': p.files,
2734+ 'desc': desc
2735+ }
2736+ p.close()
2737+ return n
2738+
2739+
2740
2741+def main():
2742+ try:
2743+ opts, args = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help'])
2744+ except getopt.error, msg:
2745+ usage(1, msg)
2746+
2747+ quiet = 0
2748+ for opt, arg in opts:
2749+ if opt in ('-h', '--help'):
2750+ usage(0)
2751+ elif opt in ('-q', '--quiet'):
2752+ quiet = 1
2753+
2754+ if len(args) <> 1:
2755+ usage(1)
2756+
2757+ lang = args[0]
2758+
2759+ isHtml = re.compile("\.html$");
2760+ isTxt = re.compile("\.txt$");
2761+
2762+ numerrors = 0
2763+ numfiles = 0
2764+ try:
2765+ files = os.listdir("templates/" + lang + "/")
2766+ except:
2767+ print "can't open templates/%s/" % lang
2768+ for file in files:
2769+ fileEN = "templates/en/" + file
2770+ fileIT = "templates/" + lang + "/" + file
2771+ errlist = []
2772+ if isHtml.search(file):
2773+ if not quiet:
2774+ print "HTML checking " + fileIT + "... "
2775+ n = check_file(fileIT, fileEN, html=1, quiet=quiet)
2776+ if n:
2777+ numerrors += n
2778+ numfiles += 1
2779+ elif isTxt.search(file):
2780+ if not quiet:
2781+ print "TXT checking " + fileIT + "... "
2782+ n = check_file(fileIT, fileEN, html=0, quiet=quiet)
2783+ if n:
2784+ numerrors += n
2785+ numfiles += 1
2786+
2787+ else:
2788+ continue
2789+
2790+ file = "messages/" + lang + "/LC_MESSAGES/mailman.po"
2791+ if not quiet:
2792+ print "PO checking " + file + "... "
2793+ n = check_po(file, quiet=quiet)
2794+ if n:
2795+ numerrors += n
2796+ numfiles += 1
2797+
2798+ if quiet:
2799+ print "%(errs)u warnings in %(files)u files" % {
2800+ 'errs': numerrors,
2801+ 'files': numfiles
2802+ }
2803+
2804+
2805
2806+if __name__ == '__main__':
2807+ main()
2808
2809=== added directory 'contrib'
2810=== renamed directory 'contrib' => 'contrib.moved'
2811=== added file 'contrib/README'
2812--- contrib/README 1970-01-01 00:00:00 +0000
2813+++ contrib/README 2010-10-14 12:15:59 +0000
2814@@ -0,0 +1,4 @@
2815+This directory contains unofficial contributed scripts and extensions
2816+to Mailman. They are unsupported by the Mailman developers. If you
2817+have questions or problems with them, please contact the contribution
2818+author directly.
2819
2820=== added file 'contrib/README.check_perms_grsecurity'
2821--- contrib/README.check_perms_grsecurity 1970-01-01 00:00:00 +0000
2822+++ contrib/README.check_perms_grsecurity 2010-10-14 12:15:59 +0000
2823@@ -0,0 +1,14 @@
2824+The check_perms_grsecurity.py script, if copied in your installed
2825+~mailman/bin/ directory and run from there will modify permissions of
2826+files so that Mailman with extra restrictions imposed by linux kernel security
2827+patches like securelinux/openwall in 2.2.x or grsecurity in 2.4.x
2828+
2829+The way it works is that it makes sure that the UID of any script that
2830+touches config.pck is `mailman'. What this means however is that
2831+scripts in ~mailman/bin will now only work if run as user mailman or
2832+root (the script then changes its UID and GID to mailman).
2833+To make grsecurity happy, we remove the group writeable bit on a directories
2834+that contain binaries.
2835+
2836+Enjoy
2837+Marc MERLIN <marc_soft@merlins.org>/<marc_bts@vasoftware.com> - 2001/12/10
2838
2839=== added file 'contrib/README.mm-handler'
2840--- contrib/README.mm-handler 1970-01-01 00:00:00 +0000
2841+++ contrib/README.mm-handler 2010-10-14 12:15:59 +0000
2842@@ -0,0 +1,215 @@
2843+Contributed by David Champion <dgc@uchicago.edu>
2844+See also ../README.SENDMAIL
2845+
2846+What?
2847+-----
2848+Mm-handler is a mail delivery agent (MDA) -- a "mailer", in Sendmail
2849+lingo. Its function is to assume authority for messages destined to
2850+Mailman lists, so that they're off sendmail's hands, and you (the site
2851+administrator) don't need to maintain databases of aliases and such. It
2852+takes a small bit of work to set up, but once that's done, you'll never
2853+need to mess with aliases for Mailman's sake again.
2854+
2855+When?
2856+-----
2857+The only further catch is that mm-handler is only really useful when
2858+it mostly "owns" its mail domain (the hostname part after an e-mail
2859+address's "@" symbol) -- when you can dedicate the mail domain to
2860+Mailman. If you have a limited set of exceptions -- a few users, for
2861+example -- you can still use it, but for sites with a dynamic or even
2862+mix of users (or forwarders) and lists, it might not gain you much.
2863+
2864+How do you know whether mm-handler is appropriate for you? Let's look
2865+at some examples. If you're running lists off your primary DNS domain
2866+name, you probably have a mix of lists and users in your namespace. Take
2867+python.org, for example: it hosts Mailman lists, and it hosts users'
2868+personal accounts. There aren't a whole bunch of either, but the ratio
2869+is probably fairly near 1:1. Mm-handler is not very useful here, because
2870+there's no simple way to separate user addresses from list addresses --
2871+not to mention that mm-handler is written in perl, so that's just bad
2872+form.
2873+
2874+This begs two different, complementary situations. A hypothetical
2875+domain, incidents.int, is used mostly for mailing lists. It's a
2876+front-end site, and not a general user mail service. There might be
2877+a couple of user addresses -- system administrators and such -- but
2878+these are few in number and easily adjusted manually by the site
2879+administrator. The 250 mailing lists at the site are much more dynamic,
2880+and a much bigger pain to keep track of by editing an alias file. This
2881+site can easily benefit from mm-handler.
2882+
2883+Inversely, mail.aero, another hypothetical domain, provides POP mail
2884+service to employees of the aerospace industry. Its addresses are
2885+almost entirely users, although it maintains a few mailing lists for
2886+convenience. This site has nothing to gain from mm-handler. It's much
2887+easier to maintain an alias file of 10 lists than to dedicate the domain
2888+to Mailman, and put all 10,000 aerospace workers in a user table.
2889+
2890+Those are the trickier cases. The case where mm-handler really works
2891+best is when you can dedicate a single hostname under your DNS domain
2892+to mailing lists, and host no user accounts there. At the University of
2893+Chicago, we do this with listhost.uchicago.edu. SourceForge does this,
2894+too, although I don't believe they use Sendmail.
2895+
2896+If your site is like that, you should read on.
2897+
2898+How?
2899+----
2900+Set-up isn't all that complicated. I've included a file here called
2901+"mailman.mc". This is the m4 file that I use on my list server, and you
2902+can likely use it with few changes at your site. It's well-annotated;
2903+the rationale for each parameter (or set of parameters) is provided in
2904+m4 (ahem) comments.
2905+
2906+So, the simple steps are as follows:
2907+
2908+1. Copy mailman.mc, and make any changes you need at your site. You
2909+ DEFINITELY need some changes. There are hostnames in there that you
2910+ need to adjust, and chances are that you'll need to change some other
2911+ parameters (like the host OS), too. [1]
2912+
2913+2. Install mm-handler. Because my server's sendmail-related files live
2914+ in /etc/mail, I keep mm-handler there, too. YMMV.
2915+
2916+3. Edit mm-handler, and make any changes you need at your site. You
2917+ probably want to change $MMWRAPPER and $MMLISTDIR at line 14, and you
2918+ *might* want to take a look at the helpful boilerplate text beginning
2919+ at line 64. (This text is sent whenever someone tries to send mail to
2920+ a nonexistent list address on your mail domain.)
2921+
2922+4. You should set up a virtusertable. (See mailman.mc for an
2923+ explanation.) There's an example of a good, minimal virtusertable
2924+ in this distribution. The virtusertable begins as a text file named
2925+ "virtusertable", stored in the same directory as all the other
2926+ Sendmail files, but it's converted to a map file for Sendmail's use.
2927+ Install the virtusertable, and (re)make the map file. [2]
2928+
2929+5. You absolutely must have a mailertable, or all of this goes nowhere.
2930+ Like virtusertable, the mailertable is a map that begins as text and
2931+ gets converted. It's named "mailertable", and it's probably pretty
2932+ simple. Mine looks like this:
2933+
2934+ listtest.uchicago.edu mailman:listtest.uchicago.edu
2935+
2936+ This says: assign all incoming mail (that was not intercepted by the
2937+ virtusertable) and that is in the listtest.uchicago.edu domain to the
2938+ "mailman" mailer, and tell the "mailman" mailer that the hostname
2939+ we're using is "listtest.uchicago.edu". You can support multiple
2940+ virtual hosts using mm-handler just by placing corresponding lines in
2941+ mailertable.
2942+
2943+ Be sure to make this map, too!
2944+
2945+6. The mailer definition (see the end of mailman.mc, or your own .mc
2946+ file) for mm-handler sets the user/group that mm-handler will run
2947+ under. (I use mailman:other.) Be sure that mm-handler is executable
2948+ by this user or group. You almost certainly need the user to be the
2949+ same as the Mailman user, and this user is almost always called
2950+ "mailman", so you probably shouldn't change the defaults.
2951+
2952+7. Generate your new sendmail.cf file. See the sendmail documentation if
2953+ you're not familiar with this procedure. [1]
2954+
2955+8. Stop sendmail on your list server, if you haven't already. Install
2956+ the new sendmail.cf file wherever your sendmail.cf file belongs.
2957+ (This depends on how sendmail was compiled, but most systems support
2958+ using /etc/sendmail.cf.)
2959+
2960+9. Cross your fingers and restart sendmail.
2961+
2962+A. Barry warns that Mailman now needs you to modify your
2963+ Mailman/mm_cfg.py file, adding this line:
2964+
2965+ MTA = None
2966+
2967+ This tells Mailman that it doesn't need to do anything special when
2968+ it creates or deletes mailing lists through the web. For more
2969+ information, take a look at the comments for this variable in your
2970+ Defaults.py file (but never edit this file -- always edit mm_cfg.py
2971+ instead!).
2972+
2973+That's it! With any luck, you're fully functional.
2974+
2975+--
2976+[1] The .mc file is the standard, supported way of configuring sendmail.
2977+ I'm not going to get into this here, and I'm not going to tell
2978+ you how to write raw sendmail.cf stuff, because if you need to do
2979+ it this way then you need something more comprehensive than I can
2980+ provide. If you need help with the .mc -> .cf process, I recommend
2981+ these links:
2982+
2983+ http://www.sendmail.org/~ca/email/setup1.html
2984+ http://www.sendmail.org/~ca/email/doc8.9/README.cf.html
2985+ http://www.hserus.net/sendmail.html
2986+
2987+[2] This is often done with something like "makemap hash
2988+ /etc/virtusertable </etc/virtusertable", but it could be different
2989+ on your server. Consult the sendmail documentation if you do not
2990+ know.
2991+
2992+
2993+The following note is provided by Kevin McNamee <kevin.mcnamee(at)symsoft.se>
2994+regarding solving a problem with mail to list addresses being rejected for
2995+"user unknown". Reference:
2996+<http://mail.python.org/pipermail/mailman-users/2006-February/049235.html>
2997+
2998+
2999+"User unknown" analysis
3000+=======================
3001+If the "user unknown" problem arises, then sendmail is not
3002+recognising your domain as a "mailman" domain.
3003+The problem could be that your mailman.mydomain.com is defined as a
3004+CNAME not a real DNS record.
3005+
3006+A hint from a tutorial about Masquerading:
3007+http://www.feep.net/sendmail/tutorial/config/masquerading.html
3008+"This address must be an address record in DNS, not simply
3009+a CNAME, or the remote end will canonicalize the address back
3010+to the original name."
3011+
3012+First confirm the problem
3013+# sendmail -bv testlist<at>mailman.mydomain.com
3014+testlist<at>mailman.mydomain.com... User unknown
3015+
3016+Then confirm that mailertable is operational
3017+# sendmail -d -bv jbloggs<at>hotmail.com | egrep "map_rewrite|mailertable"
3018+map_lookup(host, hotmail.com) => host_map_lookup(hotmail.com) =>
3019+map_rewrite(hotmail.com), av =
3020+map_rewrite => hotmail.com.
3021+map_lookup(mailertable, hotmail.com) => NOT FOUND (0)
3022+map_lookup(mailertable, .com) => NOT FOUND (0)
3023+map_lookup(mailertable, .) => NOT FOUND (0)
3024+
3025+Then confirm that your domain (CNAME) is being canonicalised:
3026+# sendmail -d -bv testlist<at>mailman.mydomain.com | egrep
3027+"map_rewrite|mailertable"
3028+map_lookup(host, mailman.mydomain.com) =>
3029+host_map_lookup(mailman.mydomain.com) => map_rewrite(aserver.mydomain.com),
3030+av =
3031+map_rewrite => aserver.mydomain.com.
3032+
3033+Sendmail has done an nslookup and found the real name of your domain which
3034+would not match your settings in mailertable (if sendmail got that far).
3035+
3036+If you remove the CNAME and create a real subdomain, then the problem will
3037+go away:
3038+# sendmail -bv testlist<at>mailman.mydomain.com
3039+testlist<at>mailman.mydomain.com... deliverable: mailer mailman, host
3040+testlist<at>mailman.mydomain.com, user testlist
3041+
3042+You will still need to create a new CNAME in your sub-domain for Apache to
3043+work.
3044+
3045+Conclusion:
3046+It is very important to make clear in the Mailman installation instructions
3047+that a REAL subdomain is needed. Those of us not familiar with DNS (or
3048+sendmail for that matter) can succeed in getting the whole Mailman
3049+installation working including the (Apache) web-interface and subscription
3050+management using just a CNAME and then wonder why we cannot send mail to our
3051+list. Hope this is of use.
3052+
3053+Ed. note: the above "conclusion" applies in this mm-handler case, but it
3054+normally does not apply if list mail is delivered via aliases.
3055+
3056+--
3057+$Id: README.mm-handler 7780 2006-02-20 03:33:19Z msapiro $
3058
3059=== added file 'contrib/README.mmdsr'
3060--- contrib/README.mmdsr 1970-01-01 00:00:00 +0000
3061+++ contrib/README.mmdsr 2010-10-14 12:15:59 +0000
3062@@ -0,0 +1,45 @@
3063+Daily Status Report script...
3064+
3065+The mmdsr script was created by Brad Knowles to produce a daily status report
3066+for mailman. It was initially posted at
3067+<http://sourceforge.net/tracker/index.php?func=detail&aid=1123383&group_id=103&atid=300103>
3068+which see for possible patches and other enhancements.
3069+
3070+Here goes the original mmdsr.readme by Brad ...
3071+
3072+========================================================================
3073+This is a basic Bourne shell script that I quickly hacked together for
3074+my own purposes, designed to be fired off at 23:59 every night, going
3075+through a variety of Mailman log files looking for entries specific
3076+to that date, summarizing the activities, and indicating problems or
3077+certain types of activity that might be of interest to someone trying
3078+to administer the server.
3079+
3080+It also does an "ls -la" of /usr/local/mailman/qfiles/*, so that you
3081+can see what is in the queue at the time of the execution of the script.
3082+
3083+This daily report will get e-mailed to the admin, or posted to a "reports"
3084+mailing list, where they can be archived and kept for future reference.
3085+If you don't define an address where the output e-mail should be sent,
3086+it will instead be printed to the standard output (thus allowing you to
3087+do something else with it).
3088+
3089+
3090+Once I'd gone through a few revisions of my own on this tool, I
3091+thought that I would release the code to the public and get comments
3092+and suggestions from others in the Mailman community. This program is
3093+currently being used actively on the mail servers for python.org (where
3094+the mailman-users and other Mailman-related mailing lists are hosted),
3095+as well as many others.
3096+
3097+Note that this script needs to be configured once to know where standard
3098+commands are located, where log files are kept, etc... (see the top
3099+500 lines or so of the script), but after that you don't need to feed
3100+it any input, or capture the output to be sent anywhere. This script
3101+takes care of all of that. All you should need to do is to call this
3102+script from a cron job at 23:59 (local time) every night.
3103+
3104+
3105+When looking at this script, perhaps during configuration, please keep
3106+in mind that it is heavily commented at the top, and everything should
3107+hopefully be self-evident.
3108
3109=== added file 'contrib/auto'
3110--- contrib/auto 1970-01-01 00:00:00 +0000
3111+++ contrib/auto 2010-10-14 12:15:59 +0000
3112@@ -0,0 +1,116 @@
3113+# -*- python -*-
3114+#
3115+# Copyright (C) 2000,2001,2002 by the Free Software Foundation, Inc.
3116+#
3117+# This program is free software; you can redistribute it and/or
3118+# modify it under the terms of the GNU General Public License
3119+# as published by the Free Software Foundation; either version 2
3120+# of the License, or (at your option) any later version.
3121+#
3122+# This program is distributed in the hope that it will be useful,
3123+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3124+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3125+# GNU General Public License for more details.
3126+#
3127+# You should have received a copy of the GNU General Public License
3128+# along with this program; if not, write to the Free Software
3129+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
3130+
3131+"""Automatically send a message to a mailing list.
3132+"""
3133+
3134+# To use with Postfix, set the following in your main.cf file:
3135+#
3136+# recipient_delimiter = +
3137+# luser_relay = mm+$user@yourdomain.com
3138+# owner_request_special = no
3139+
3140+import sys
3141+import os
3142+import time
3143+
3144+import paths
3145+from Mailman import mm_cfg
3146+from Mailman import Utils
3147+from Mailman import MailList
3148+from Mailman import Errors
3149+from Mailman.Queue.sbcache import get_switchboard
3150+from Mailman.Logging.Utils import LogStdErr
3151+
3152+# Error code if it's really not a Mailman list addr destination
3153+EX_NOUSER = 67
3154+
3155+LogStdErr('auto', 'auto')
3156+
3157+DISPOSE_MAP = {None : 'tolist',
3158+ 'request': 'torequest',
3159+ 'admin' : 'toadmin',
3160+ 'owner' : 'toadmin',
3161+ }
3162+
3163+
3164+
3165
3166+def fqdn_listname(listname, hostname):
3167+ return ('%s@%s' % (listname, hostname)).lower()
3168+
3169+
3170+
3171
3172+def main():
3173+ # Postfix sets some environment variables based on information gleaned
3174+ # from the original message. This is the most direct way to figure out
3175+ # which list the message was intended for.
3176+ extension = os.environ.get('EXTENSION', '').lower()
3177+ i = extension.rfind('-')
3178+ if i < 0:
3179+ listname = extension
3180+ subdest = 'tolist'
3181+ else:
3182+ missing = []
3183+ listname = extension[:i]
3184+ subdest = DISPOSE_MAP.get(extension[i+1:], missing)
3185+ if not Utils.list_exists(listname) or subdest is missing:
3186+ # must be a list that has a `-' in it's name
3187+ listname = extension
3188+ subdest = 'tolist'
3189+ if not listname:
3190+ print >> sys.stderr, 'Empty list name (someone being subversive?)'
3191+ return EX_NOUSER
3192+ try:
3193+ mlist = MailList.MailList(listname, lock=0)
3194+ except Errors.MMListError:
3195+ print >> sys.stderr, 'List not found:', listname
3196+ return EX_NOUSER
3197+
3198+ # Make sure that the domain part of the incoming address matches the
3199+ # domain of the mailing list. Actually, it's possible that one or the
3200+ # other is more fully qualified, and thus longer. So we split the domains
3201+ # by dots, reverse them and make sure that whatever parts /are/ defined
3202+ # for both are equivalent.
3203+ domain = os.environ.get('DOMAIN', '').lower()
3204+ domainp = domain.split('.')
3205+ hostname = mlist.host_name.split('.')
3206+ domainp.reverse()
3207+ hostname.reverse()
3208+ for ca, cb in zip(domainp, hostname):
3209+ if ca <> cb:
3210+ print >> sys.stderr, 'Domain mismatch: %s@%s (expected @%s)' \
3211+ % (listname, domain, mlist.host_name)
3212+ return EX_NOUSER
3213+
3214+ if subdest is None:
3215+ print >> sys.stderr, 'Bad sub-destination:', extension
3216+ return EX_NOUSER
3217+
3218+ inq = get_switchboard(mm_cfg.INQUEUE_DIR)
3219+ inq.enqueue(sys.stdin.read(),
3220+ listname=listname,
3221+ received_time=time.time(),
3222+ _plaintext=1,
3223+ **{subdest: 1})
3224+ return 0
3225+
3226+
3227+
3228
3229+if __name__ == '__main__':
3230+ code = main()
3231+ sys.exit(code)
3232
3233=== added file 'contrib/check_perms_grsecurity.py'
3234--- contrib/check_perms_grsecurity.py 1970-01-01 00:00:00 +0000
3235+++ contrib/check_perms_grsecurity.py 2010-10-14 12:15:59 +0000
3236@@ -0,0 +1,182 @@
3237+#! @PYTHON@
3238+#
3239+# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
3240+#
3241+# This program is free software; you can redistribute it and/or
3242+# modify it under the terms of the GNU General Public License
3243+# as published by the Free Software Foundation; either version 2
3244+# of the License, or (at your option) any later version.
3245+#
3246+# This program is distributed in the hope that it will be useful,
3247+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3248+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3249+# GNU General Public License for more details.
3250+#
3251+# You should have received a copy of the GNU General Public License
3252+# along with this program; if not, write to the Free Software
3253+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
3254+# USA.
3255+
3256+"""Fixes for running Mailman under the `secure-linux' patch or grsecurity.
3257+
3258+Run check_perms -f and only then check_perms_grsecurity.py -f
3259+Note that you will have to re-run this script after a mailman upgrade and
3260+that check_perms will undo part of what this script does
3261+
3262+If you use Solar Designer's secure-linux patch, it prevents a process from
3263+linking (hard link) to a file it doesn't own.
3264+Grsecurity (http://grsecurity.net/) can have the same restriction depending
3265+on how it was built, including other restrictions like preventing you to run
3266+a program if it is located in a directory writable by a non root user.
3267+
3268+As a result Mailman has to be changed so that the whole tree is owned by
3269+Mailman, and the CGIs and some of the programs in the bin tree (the ones
3270+that lock config.pck files) are SUID Mailman. The idea is that config.pck
3271+files have to be owned by the mailman UID and only touched by programs that
3272+are UID mailman.
3273+At the same time, We have to make sure that at least 3 directories under
3274+~mailman aren't writable by mailman: mail, cgi-bin, and bin
3275+
3276+Binary commands that are changed to be SUID mailman are also made unreadable
3277+and unrunnable by people who aren't in the mailman group. This shouldn't
3278+affect much since most of those commands would fail work if you weren't part
3279+of the mailman group anyway.
3280+Scripts in ~mailman/bin/ are not made suid or sgid, they need to be run by
3281+user mailman or root to work.
3282+
3283+Marc <marc_soft@merlins.org>/<marc_bts@vasoftware.com>
3284+2000/10/27 - Initial version for secure_linux/openwall and mailman 2.0
3285+2001/12/09 - Updated version for grsecurity and mailman 2.1
3286+"""
3287+
3288+import sys
3289+import os
3290+import paths
3291+import re
3292+import glob
3293+import pwd
3294+import grp
3295+from Mailman import mm_cfg
3296+from Mailman.mm_cfg import MAILMAN_USER, MAILMAN_GROUP
3297+from stat import *
3298+
3299+# Directories that we don't want writable by mailman.
3300+dirstochownroot= ( 'mail', 'cgi-bin', 'bin' )
3301+
3302+# Those are the programs that we patch so that they insist being run under the
3303+# mailman uid or as root.
3304+binfilestopatch= ( 'add_members', 'change_pw', 'check_db', 'clone_member',
3305+ 'config_list', 'newlist', 'qrunner', 'remove_members',
3306+ 'rmlist', 'sync_members', 'update', 'withlist' )
3307+
3308+def main(argv):
3309+ binpath = paths.prefix + '/bin/'
3310+ droplib = binpath + 'CheckFixUid.py'
3311+
3312+ if len(argv) < 2 or argv[1] != "-f":
3313+ print __doc__
3314+ sys.exit(1)
3315+
3316+ print "Making select directories owned and writable by root only"
3317+ gid = grp.getgrnam(MAILMAN_GROUP)[2]
3318+ for dir in dirstochownroot:
3319+ dirpath = paths.prefix + '/' + dir
3320+ os.chown(dirpath, 0, gid)
3321+ os.chmod(dirpath, 02755)
3322+ print dirpath
3323+
3324+ print
3325+
3326+ file = paths.prefix + '/data/last_mailman_version'
3327+ print "Making" + file + "owned by mailman (not root)"
3328+ uid = pwd.getpwnam(MAILMAN_USER)[2]
3329+ gid = grp.getgrnam(MAILMAN_GROUP)[2]
3330+ os.chown(file, uid, gid)
3331+ print
3332+
3333+ if not os.path.exists(droplib):
3334+ print "Creating " + droplib
3335+ fp = open(droplib, 'w', 0644)
3336+ fp.write("""import sys
3337+import os
3338+import grp, pwd
3339+from Mailman.mm_cfg import MAILMAN_USER, MAILMAN_GROUP
3340+
3341+class CheckFixUid:
3342+ uid = pwd.getpwnam(MAILMAN_USER)[2]
3343+ gid = grp.getgrnam(MAILMAN_GROUP)[2]
3344+ if os.geteuid() == 0:
3345+ os.setgid(gid)
3346+ os.setuid(uid)
3347+ if os.geteuid() != uid:
3348+ print "You need to run this script as root or mailman because it was configured to run"
3349+ print "on a linux system with a security patch which restricts hard links"
3350+ sys.exit()
3351+""")
3352+ fp.close()
3353+ else:
3354+ print "Skipping creation of " + droplib
3355+
3356+
3357+ print "\nMaking cgis setuid mailman"
3358+ cgis = glob.glob(paths.prefix + '/cgi-bin/*')
3359+
3360+ for file in cgis:
3361+ print file
3362+ os.chown(file, uid, gid)
3363+ os.chmod(file, 06755)
3364+
3365+ print "\nMaking mail wrapper setuid mailman"
3366+ file= paths.prefix + '/mail/mailman'
3367+ os.chown(file, uid, gid)
3368+ os.chmod(file, 06755)
3369+ print file
3370+
3371+ print "\nEnsuring that all config.db/pck files are owned by Mailman"
3372+ cdbs = glob.glob(paths.prefix + '/lists/*/config.db*')
3373+ cpcks = glob.glob(paths.prefix + '/lists/*/config.pck*')
3374+
3375+ for file in cdbs + cpcks:
3376+ stat = os.stat(file)
3377+ if (stat[ST_UID] != uid or stat[ST_GID] != gid):
3378+ print file
3379+ os.chown(file, uid, gid)
3380+
3381+ print "\nPatching mailman scripts to change the uid to mailman"
3382+
3383+ for script in binfilestopatch:
3384+ filefd = open(script, "r")
3385+ file = filefd.readlines()
3386+ filefd.close()
3387+
3388+ patched = 0
3389+ try:
3390+ file.index("import CheckFixUid\n")
3391+ print "Not patching " + script + ", already patched"
3392+ except ValueError:
3393+ file.insert(file.index("import paths\n")+1, "import CheckFixUid\n")
3394+ for i in range(len(file)-1, 0, -1):
3395+ object=re.compile("^([ ]*)main\(").search(file[i])
3396+ # Special hack to support patching of update
3397+ object2=re.compile("^([ ]*).*=[ ]*main\(").search(file[i])
3398+ if object:
3399+ print "Patching " + script
3400+ file.insert(i,
3401+ object.group(1) + "CheckFixUid.CheckFixUid()\n")
3402+ patched=1
3403+ break
3404+ if object2:
3405+ print "Patching " + script
3406+ file.insert(i,
3407+ object2.group(1) + "CheckFixUid.CheckFixUid()\n")
3408+ patched=1
3409+ break
3410+
3411+ if patched==0:
3412+ print "Warning, file "+script+" couldn't be patched."
3413+ print "If you use it, mailman may not function properly"
3414+ else:
3415+ filefd=open(script, "w")
3416+ filefd.writelines(file)
3417+
3418+main(sys.argv)
3419
3420=== added file 'contrib/mailman.mc'
3421--- contrib/mailman.mc 1970-01-01 00:00:00 +0000
3422+++ contrib/mailman.mc 2010-10-14 12:15:59 +0000
3423@@ -0,0 +1,143 @@
3424+dnl
3425+dnl *** EXAMPLE *** sendmail.mc file for a Mailman list server using
3426+dnl mm-handler to deal with list operations (in place of aliases).
3427+dnl This is what I actually use on my site.
3428+dnl
3429+dnl $Id: mailman.mc 4287 2001-10-27 02:30:51Z bwarsaw $
3430+dnl
3431+
3432+
3433+dnl
3434+dnl First you need to define your general characteristics. You
3435+dnl should know what these settings should be at your site -- I
3436+dnl only know what they should be at mine.
3437+dnl
3438+OSTYPE(solaris2)dnl
3439+DOMAIN(generic)dnl
3440+
3441+dnl
3442+dnl You can keep the old alias files for back-compatibility, but it's
3443+dnl probably better not to as this can become a point of confusion
3444+dnl later.
3445+dnl
3446+define(`ALIAS_FILE', `/etc/mail/aliases,/etc/mail/lists')
3447+
3448+dnl
3449+dnl I use procmail for local delivery, because it's smart to have a
3450+dnl local delivery mailer, even if you don't (ordinarily) do any local
3451+dnl delivery. The Solaris local delivery mailer is part of its sendmail
3452+dnl package. I pkgrmed the sendmail packages so that system upgrades
3453+dnl don't kill my sendmail.com sendmail, so mail.local is unavailable,
3454+dnl so I throw procmail in here even though it never gets used.
3455+dnl
3456+define(`PROCMAIL_MAILER_PATH', `/opt/bin/procmail')
3457+FEATURE(`local_procmail')
3458+MAILER(`local')
3459+
3460+dnl
3461+dnl Miscellaneous tuning. Not relevant to Mailman.
3462+dnl
3463+define(`confCONNECTION_RATE_THROTTLE', 5)
3464+define(`confMAX_MESSAGE_SIZE', `5000000')
3465+define(`confNO_RCPT_ACTION', `add-to-undisclosed')
3466+define(`confME_TOO', `True')
3467+define(`confDOUBLE_BOUNCE_ADDRESS', `mailer-daemon')
3468+
3469+dnl
3470+dnl Privacy options. Also not relevant.
3471+dnl
3472+define(`confPRIVACY_FLAGS', `authwarnings,needvrfyhelo,noexpn,noreceipts,restrictmailq')
3473+
3474+
3475+dnl
3476+dnl Mm-handler works by mailertabling all addresses on your list
3477+dnl server hostname(s) through the mm-handler mailer. Mailertable
3478+dnl maps mail domains to mailer types. I want a mailertable to map
3479+dnl listtest.uchicago.edu to the mm-handler mailer, but we need to
3480+dnl specifically request this functionality in the .mc file.
3481+dnl
3482+FEATURE(`mailertable', `hash -o /etc/mail/mailertable')
3483+
3484+dnl
3485+dnl This leads to an immediate and important side-effect: "local"
3486+dnl addresses, and notably RFC-specified addresses such as postmaster,
3487+dnl are assumed by sendmail to be lists! Since aliases are not processed
3488+dnl for domaintabled domains, we must use a virtusertable to reroute
3489+dnl such addresses.
3490+dnl
3491+FEATURE(`virtusertable', `hash -o /etc/mail/virtusertable')
3492+
3493+dnl
3494+dnl By default, sendmail applies virtusertable mapping, if at all, for
3495+dnl all interfaces for which it accepts mail -- i.e., all domains in
3496+dnl $=w. Mm-handler relies on your having a single domain (hostname)
3497+dnl that serves only lists, with no users. To avoid potential namespace
3498+dnl conflicts, you need not to have this list domain included in $=w.
3499+dnl As a result, virtuser mapping does not apply for the Mailman
3500+dnl list domain. However, you can pre-empt this rule by defining
3501+dnl $={VirtHost}: if there are domains in this class, they will be
3502+dnl mapped before $=w is mapped.
3503+dnl
3504+dnl VIRTUSER_DOMAIN() defines this class.
3505+dnl
3506+VIRTUSER_DOMAIN(`nospam.uchicago.edu listtest.uchicago.edu listhost.uchicago.edu')
3507+
3508+dnl
3509+dnl On a related point: by default, Sendmail probes for open IP
3510+dnl interfaces, and adds their hostnames to $=w. Although Sendmail does
3511+dnl virtusertable mapping for members of $=w, it doesn't do mailertable
3512+dnl mapping for them, because they're considered "local". This tells
3513+dnl Sendmail not to probe interfaces for local hosts, and it's critical
3514+dnl if your Mailman domain is actually an IP address (with an A record,
3515+dnl not just CNAME or MX) on your server.
3516+dnl
3517+define(`confDONT_PROBE_INTERFACES', `True')
3518+
3519+
3520+dnl
3521+dnl Even though my actual hostname is foobar, tell the world that I'm
3522+dnl listtest.uchicago.edu.
3523+dnl
3524+FEATURE(`limited_masquerade')
3525+MASQUERADE_AS(`listtest.uchicago.edu')
3526+
3527+
3528+dnl
3529+dnl Access control is a useful feature for blocking abusers and relays
3530+dnl and such.
3531+dnl
3532+FEATURE(`access_db')
3533+
3534+
3535+dnl
3536+dnl This allows you to block access for individual recipents through
3537+dnl the same access database as is used for blocking sender hosts and
3538+dnl addresses.
3539+dnl
3540+FEATURE(`blacklist_recipients')
3541+
3542+
3543+dnl
3544+dnl Other local mailers...
3545+dnl
3546+MAILER(`smtp')
3547+MAILER(`procmail')
3548+
3549+
3550+dnl
3551+dnl Our Mailman-specific local mailer.
3552+dnl
3553+MAILER_DEFINITIONS
3554+####################################
3555+### New Mailer specifications ###
3556+####################################
3557+
3558+## Special flags! See
3559+## http://www.sendmail.org/~ca/email/doc8.10/op-sh-5.html#sh-5.4
3560+## Note especially the absence of the "m" and "n" flags. THIS IS
3561+## IMPORTANT: mm-handler assumes this behavior to avoid having to know
3562+## too much about address parsing and other RFC-2822 mail details.
3563+
3564+Mmailman, P=/etc/mail/mm-handler, F=rDFMhlqSu, U=mailman:other,
3565+ S=EnvFromL, R=EnvToL/HdrToL,
3566+ A=mm-handler $h $u
3567
3568=== added file 'contrib/majordomo2mailman.pl'
3569--- contrib/majordomo2mailman.pl 1970-01-01 00:00:00 +0000
3570+++ contrib/majordomo2mailman.pl 2010-10-14 12:15:59 +0000
3571@@ -0,0 +1,691 @@
3572+#!/usr/bin/perl -w
3573+
3574+# majordomo2mailman.pl - Migrate Majordomo mailing lists to Mailman 2.0
3575+# Copyright (C) 2002 Heiko Rommel (rommel@suse.de)
3576+
3577+# BAW: Note this probably needs to be upgraded to work with MM2.1
3578+
3579+#
3580+# License:
3581+#
3582+# This program is free software; you can redistribute it and/or modify
3583+# it under the terms of the GNU General Public License as published by
3584+# the Free Software Foundation; either version 1, or (at your option)
3585+# any later version.
3586+
3587+#
3588+# Warranty:
3589+#
3590+# There's absolutely no warranty.
3591+#
3592+
3593+# comments on possible debug messages during the conversion:
3594+#
3595+# "not an valid email address" : those addresses are rejected, i.e. not imported into the Mailman list
3596+# "not a numeric value" : such a value will be converted to 0 (z.B. maxlength)
3597+# "already subscribed" : will only once be subscribed on the Mailman list
3598+# "...umbrella..." or "...taboo..." -> Mailman-Admin-Guide
3599+
3600+use strict;
3601+use Getopt::Long;
3602+use Fcntl;
3603+use POSIX qw (tmpnam);
3604+
3605+use vars qw (
3606+ $majordomo $mydomain $myurl
3607+ $aliasin $listdir
3608+ $aliasout $mailmanbin
3609+ $umbrella_member_suffix $private
3610+ $newsserver $newsprefix
3611+ $susehack $susearchuser
3612+ $help $debug $update $all $usagemsg
3613+ *FH
3614+ %mlaliases %mlowners %mlapprovers
3615+ %defaultmlconf %mlconf
3616+ %defaultmmconf %mmconf
3617+);
3618+
3619+#
3620+# adjust your site-specific settings here
3621+#
3622+
3623+$mydomain = "my.domain";
3624+$majordomo = "majordomo"; # the master Majordomo address for your site
3625+$aliasin = "/var/lib/majordomo/aliases";
3626+$listdir = "/var/lib/majordomo/lists";
3627+$aliasout = "/tmp/aliases";
3628+$myurl = "http://my.domain/mailman/";
3629+$mailmanbin = "/usr/lib/mailman/bin";
3630+$umbrella_member_suffix = "-owner";
3631+$private = "yes"; # is this a private/Intranet site ?
3632+$newsserver = "news.my.domain";
3633+$newsprefix = "intern.";
3634+
3635+$susehack = "no";
3636+$susearchuser = "archdummy";
3637+
3638+#
3639+# 0)
3640+# parse the command line arguments
3641+#
3642+
3643+$usagemsg = "usage: majordomo2mailman [-h|--help] [-d|--debug] [-u|--update] < (-a|--all) | list-of-mailinglists >";
3644+
3645+GetOptions(
3646+ "h|help" => \$help,
3647+ "d|debug" => \$debug,
3648+ "a|all" => \$all,
3649+ "u|update" => \$update
3650+) or die "$usagemsg\n";
3651+
3652+if (defined($help)) { die "$usagemsg\n"; }
3653+
3654+if ((not defined($all)) and (@ARGV<1)) { die "$usagemsg\n"; }
3655+
3656+if ($<) { die "this script must be run as root!\n"; }
3657+
3658+#
3659+# 1)
3660+# build a list of all aliases and extract the name of mailing lists plus their owners
3661+#
3662+
3663+%mlaliases = %mlowners = %mlapprovers = ();
3664+
3665+open (FH, "< $aliasin") or die "can't open $aliasin\n";
3666+
3667+while (<FH>) {
3668+ # first, build a list of all active aliases and their resolution
3669+ if (/^([^\#:]+)\s*:\s*(.*)$/) {
3670+ $mlaliases{$1} = $2;
3671+ }
3672+}
3673+
3674+my $mlalias;
3675+for $mlalias (keys %mlaliases) {
3676+ # if we encounter an alias with :include: as expansion
3677+ # it is save to assume that the alias has the form
3678+ # <mailinglist>-outgoing -
3679+ # that way we find the names of all active mailing lists
3680+ if ($mlaliases{$mlalias} =~ /\:include\:/) {
3681+ my $ml;
3682+ ($ml = $mlalias) =~ s/-outgoing//g;
3683+ $mlowners{$ml} = $mlaliases{"owner-$ml"};
3684+ $mlapprovers{$ml} = $mlaliases{"$ml-approval"};
3685+ }
3686+}
3687+
3688+close (FH);
3689+
3690+#
3691+# 2)
3692+# for each list read the Majordomo configuration params
3693+# and create a Mailman clone
3694+#
3695+
3696+my $ml;
3697+for $ml ((defined ($all)) ? sort keys %mlowners : @ARGV) {
3698+
3699+ init_defaultmlconf($ml);
3700+ %mlconf = %defaultmlconf;
3701+
3702+ init_defaultmmconf($ml);
3703+ %mmconf = %defaultmmconf;
3704+
3705+ my @privileged; # addresses that are mentioned in restrict_post
3706+ my @members;
3707+ my ($primaryowner, @secondaryowner);
3708+ my ($primaryapprover, @secondaryapprover);
3709+
3710+ my ($skey, $terminator);
3711+ my $filename;
3712+ my @args;
3713+
3714+ #
3715+ # a)
3716+ # parse the configuration file
3717+ #
3718+
3719+ open (FH, "< $listdir/$ml.config") or die "can't open $listdir/$ml.config\n";
3720+
3721+ while (<FH>) {
3722+ # key = value ?
3723+ if (/^\s*([^=\#\s]+)\s*=\s*(.*)\s*$/) {
3724+ $mlconf{$1} = $2;
3725+ }
3726+ # key << EOF
3727+ # value
3728+ # EOF ?
3729+ elsif (/^\s*([^<\#\s]+)\s*<<\s*(.*)\s*$/) {
3730+ ($skey, $terminator) = ($1, $2);
3731+ while (<FH>) {
3732+ last if (/^$terminator\s*$/);
3733+ $mlconf{$skey} .= $_;
3734+ }
3735+ chomp $mlconf{$skey};
3736+ }
3737+ }
3738+
3739+ close (FH);
3740+
3741+ #
3742+ # b)
3743+ # test if there are so-called flag files (clue that this is an old-style Majordomo lists)
3744+ # and overwrite previously parsed values
3745+ # (stolen from majordomo::config_parse.pl: handle_flag_files())
3746+ #
3747+
3748+ if ( -e "$listdir/$ml.private") {
3749+ $mlconf{"get_access"} = "closed";
3750+ $mlconf{"index_access"} = "closed";
3751+ $mlconf{"who_access"} = "closed";
3752+ $mlconf{"which_access"} = "closed";
3753+ }
3754+
3755+ $mlconf{"subscribe_policy"} = "closed" if ( -e "$listdir/$ml.closed");
3756+ $mlconf{"unsubscribe_policy"} = "closed" if ( -e "$listdir/$ml.closed");
3757+
3758+ if ( -e "$listdir/$ml.auto" && -e "$listdir/$ml.closed") {
3759+ print STDERR "sowohl $ml.auto als auch $ml.closed existieren. Wähle $ml.closed\n";
3760+ }
3761+ else {
3762+ $mlconf{"subscribe_policy"} = "auto" if ( -e"$listdir/$ml.auto");
3763+ $mlconf{"unsubscribe_policy"} = "auto" if ( -e"$listdir/$ml.auto");
3764+ }
3765+
3766+ $mlconf{"strip"} = 1 if ( -e "$listdir/$ml.strip");
3767+ $mlconf{"noadvertise"} = "/.*/" if ( -e "$listdir/$ml.hidden");
3768+
3769+ # admin_passwd:
3770+ $filename = "$listdir/" . $mlconf{"admin_passwd"};
3771+ if ( -e "$listdir/$ml.passwd" ) {
3772+ $mlconf{"admin_passwd"} = read_from_file("$listdir/$ml.passwd");
3773+ }
3774+ elsif ( -e "$filename" ) {
3775+ $mlconf{"admin_passwd"} = read_from_file("$filename");
3776+ }
3777+ # else take it verbatim
3778+
3779+ # approve_passwd:
3780+ $filename = "$listdir/" . $mlconf{"approve_passwd"};
3781+ if ( -e "$listdir/$ml.passwd" ) {
3782+ $mlconf{"approve_passwd"} = read_from_file("$listdir/$ml.passwd");
3783+ }
3784+ elsif ( -e "$filename" ) {
3785+ $mlconf{"approve_passwd"} = read_from_file("$filename");
3786+ }
3787+ # else take it verbatim
3788+
3789+ #
3790+ # c)
3791+ # add some information from additional configuration files
3792+ #
3793+
3794+ # restrict_post
3795+ if (defined ($mlconf{"restrict_post"})) {
3796+ @privileged = ();
3797+ for $filename (split /\s+/, $mlconf{"restrict_post"}) {
3798+ open (FH, "< $listdir/$filename") or die "can't open $listdir/$filename\n";
3799+ push (@privileged, <FH>);
3800+ chomp @privileged;
3801+ close (FH);
3802+ }
3803+ }
3804+
3805+ if ($susehack =~ m/yes/i) {
3806+ @privileged = grep(!/$susearchuser\@$mydomain/i, @privileged);
3807+ }
3808+
3809+ $mlconf{"privileged"} = \@privileged;
3810+
3811+ # members
3812+ @members = ();
3813+ open (FH, "< $listdir/$ml") or die "can't open $listdir/$ml\n";
3814+ push (@members, <FH>);
3815+ chomp @members;
3816+ close (FH);
3817+
3818+ $mlconf{"gated"} = "no";
3819+
3820+ if ($susehack =~ m/yes/i) {
3821+ if (grep(/$susearchuser\@$mydomain/i, @members)) {
3822+ $mlconf{"gated"} = "yes";
3823+ }
3824+ @members = grep(!/$susearchuser\@$mydomain/i, @members);
3825+ }
3826+
3827+ $mlconf{"members"} = \@members;
3828+
3829+ # intro message
3830+ if (open (FH, "< $listdir/$ml.intro")) {
3831+ { local $/; $mlconf{"intro"} = <FH>; }
3832+ }
3833+ else { $mlconf{"intro"} = ""; }
3834+
3835+ # info message
3836+ if (open (FH, "< $listdir/$ml.info")) {
3837+ { local $/; $mlconf{"info"} = <FH>; }
3838+ }
3839+ else { $mlconf{"info"} = ""; }
3840+
3841+ #
3842+ # d)
3843+ # take over some other params into the configuration table
3844+ #
3845+
3846+ $mlconf{"name"} = "$ml";
3847+
3848+ ($primaryowner, @secondaryowner) =
3849+ expand_alias (split (/\s*,\s*/, aliassub($mlowners{$ml})));
3850+
3851+ ($primaryapprover, @secondaryapprover) =
3852+ expand_alias (split (/\s*,\s*/, aliassub($mlapprovers{$ml})));
3853+
3854+ $mlconf{"primaryowner"} = $primaryowner;
3855+ $mlconf{"secondaryowner"} = \@secondaryowner;
3856+
3857+ $mlconf{"primaryapprover"} = $primaryapprover;
3858+ $mlconf{"secondaryapprover"} = \@secondaryapprover;
3859+
3860+ #
3861+ # debugging output
3862+ #
3863+
3864+ if (defined ($debug)) {
3865+ print "##################### $ml ####################\n";
3866+ for $skey (sort keys %mlconf) {
3867+ if (defined ($mlconf{$skey})) { print "$skey = $mlconf{$skey}\n"; }
3868+ else { print "$skey = (?)\n"; }
3869+ }
3870+ my $priv;
3871+ for $priv (@privileged) {
3872+ print "\t$ml: $priv\n";
3873+ }
3874+ }
3875+
3876+ #
3877+ # e)
3878+ # with the help of Mailman commands - create a new list and subscribe the old staff
3879+ #
3880+
3881+ if (defined($update)) {
3882+ print "updating configuration of \"$ml\"\n";
3883+ }
3884+ else {
3885+ # Mailman lists can initially be only created with one owner
3886+ @args = ("$mailmanbin/newlist", "-q", "-o", "$aliasout", "$ml", $mlconf{"primaryowner"}, $mlconf{"admin_passwd"});
3887+ system (@args) == 0 or die "system @args failed: $?";
3888+ }
3889+
3890+ # Mailman accepts only subscriber lists > 0
3891+ if (@members > 0) {
3892+ $filename = tmpnam();
3893+ open (FH, "> $filename") or die "can't open $filename\n";
3894+ for $skey (@members) {
3895+ print FH "$skey" . "\n";
3896+ }
3897+ close (FH);
3898+ @args = ("$mailmanbin/add_members", "-n", "$filename", "--welcome-msg=n", "$ml");
3899+ system (@args) == 0 or die "system @args failed: $?";
3900+ }
3901+
3902+ #
3903+ # f)
3904+ # "translate" the Majordomo list configuration
3905+ #
3906+
3907+ m2m();
3908+
3909+ # write the Mailman config
3910+
3911+ $filename = tmpnam();
3912+
3913+ open (FH, "> $filename") or die "can't open $filename\n";
3914+ for $skey (sort keys %mmconf) {
3915+ print FH "$skey = " . $mmconf{$skey} . "\n";
3916+ }
3917+ close (FH);
3918+
3919+ @args = ("$mailmanbin/config_list", "-i", "$filename", "$ml");
3920+ system (@args) == 0 or die "system @args failed: $?";
3921+
3922+ unlink($filename) or print STDERR "unable to unlink \"$filename\"!\n";
3923+
3924+}
3925+
3926+exit 0;
3927+
3928+#############
3929+# subs
3930+#############
3931+
3932+#
3933+# I don't know how to write Perl code
3934+# therefor I need this stupid procedure to cleanly read a value from file
3935+#
3936+
3937+sub read_from_file {
3938+ my $value;
3939+ local *FH;
3940+
3941+ open (FH, "< $_[0]") or die "can't open $_[0]\n";
3942+ $value = <FH>;
3943+ chomp $value;
3944+ close (FH);
3945+
3946+ return $value;
3947+}
3948+
3949+
3950+#
3951+# add "@$mydomain" to each element that does not contain a "@"
3952+#
3953+
3954+sub expand_alias {
3955+ return map { (not $_ =~ /@/) ? $_ .= "\@$mydomain" : $_ } @_;
3956+}
3957+
3958+#
3959+# replace the typical owner-majordomo aliases
3960+#
3961+
3962+sub aliassub {
3963+ my $string = $_[0];
3964+
3965+ $string =~ s/(owner-$majordomo|$majordomo-owner)/mailman-owner/gi;
3966+
3967+ return $string;
3968+}
3969+
3970+#
3971+# default values of Majordomo mailing lists
3972+# (stolen from majordomo::config_parse.pl: %known_keys)
3973+#
3974+
3975+sub init_defaultmlconf {
3976+ my $ml = $_[0];
3977+
3978+ %defaultmlconf=(
3979+ 'welcome', "yes",
3980+ 'announcements', "yes",
3981+ 'get_access', "open",
3982+ 'index_access', "open",
3983+ 'who_access', "open",
3984+ 'which_access', "open",
3985+ 'info_access', "open",
3986+ 'intro_access', "open",
3987+ 'advertise', "",
3988+ 'noadvertise', "",
3989+ 'description', "",
3990+ 'subscribe_policy', "open",
3991+ 'unsubscribe_policy', "open",
3992+ 'mungedomain', "no",
3993+ 'admin_passwd', "$ml.admin",
3994+ 'strip', "yes",
3995+ 'date_info', "yes",
3996+ 'date_intro', "yes",
3997+ 'archive_dir', "",
3998+ 'moderate', "no",
3999+ 'moderator', "",
4000+ 'approve_passwd', "$ml.pass",
4001+ 'sender', "owner-$ml",
4002+ 'maxlength', "40000",
4003+ 'precedence', "bulk",
4004+ 'reply_to', "",
4005+ 'restrict_post', "",
4006+ 'purge_received', "no",
4007+ 'administrivia', "yes",
4008+ 'resend_host', "",
4009+ 'debug', "no",
4010+ 'message_fronter', "",
4011+ 'message_footer', "",
4012+ 'message_headers', "",
4013+ 'subject_prefix', "",
4014+ 'taboo_headers', "",
4015+ 'taboo_body', "",
4016+ 'digest_volume', "1",
4017+ 'digest_issue', "1",
4018+ 'digest_work_dir', "",
4019+ 'digest_name', "$ml",
4020+ 'digest_archive', "",
4021+ 'digest_rm_footer', "",
4022+ 'digest_rm_fronter', "",
4023+ 'digest_maxlines', "",
4024+ 'digest_maxdays', "",
4025+ 'comments', ""
4026+ );
4027+}
4028+
4029+
4030+#
4031+# Mailman mailing list params that are not derived from Majordomo mailing lists params
4032+# (e.g. bounce_matching_headers+forbbiden_posters vs. taboo_headers+taboo_body)
4033+# If you need one of this params to be variable remove it here and add some code to the
4034+# main procedure; additionally, you should compare it with what you have in
4035+# /usr/lib/mailman/Mailman/mm_cfg.py
4036+#
4037+
4038+sub init_defaultmmconf {
4039+
4040+ %defaultmmconf=(
4041+ 'goodbye_msg', "\'\'",
4042+ 'umbrella_list', "0",
4043+ 'umbrella_member_suffix', "\'$umbrella_member_suffix\'",
4044+ 'send_reminders', "0",
4045+ 'admin_immed_notify', "1",
4046+ 'admin_notify_mchanges', "0",
4047+ 'dont_respond_to_post_requests', "0",
4048+ 'obscure_addresses', "1",
4049+ 'require_explicit_destination', "1",
4050+ 'acceptable_aliases', "\"\"\"\n\"\"\"\n",
4051+ 'max_num_recipients', "10",
4052+ 'forbidden_posters', "[]",
4053+ 'bounce_matching_headers', "\"\"\"\n\"\"\"\n",
4054+ 'anonymous_list', "0",
4055+ 'nondigestable', "1",
4056+ 'digestable', "1",
4057+ 'digest_is_default', "0",
4058+ 'mime_is_default_digest', "0",
4059+ 'digest_size_threshhold', "40",
4060+ 'digest_send_periodic', "1",
4061+ 'digest_header', "\'\'",
4062+ 'bounce_processing', "1",
4063+ 'minimum_removal_date', "4",
4064+ 'minimum_post_count_before_bounce_action', "3",
4065+ 'max_posts_between_bounces', "5",
4066+ 'automatic_bounce_action', "3",
4067+ 'archive_private', "0",
4068+ 'clobber_date', "1",
4069+ 'archive_volume_frequency', "1",
4070+ 'autorespond_postings', "0",
4071+ 'autoresponse_postings_text', "\'\'",
4072+ 'autorespond_admin', "0",
4073+ 'autoresponse_admin_text', "\'\'",
4074+ 'autorespond_requests', "0",
4075+ 'autoresponse_request_text', "\'\'",
4076+ 'autoresponse_graceperiod', "90"
4077+ );
4078+}
4079+
4080+#
4081+# convert a Majordomo mailing list configuration (%mlconf) into a
4082+# Mailman mailing list configuration (%mmconf)
4083+# only those params are affected which can be derived from Majordomo
4084+# mailing list configurations
4085+#
4086+
4087+sub m2m {
4088+
4089+ my $elem;
4090+ my $admin;
4091+
4092+ $mmconf{"real_name"} = "\'" . $mlconf{"name"} . "\'";
4093+
4094+ # Mailman does not know the difference between owner and approver
4095+ for $admin (($mlconf{"primaryowner"}, @{$mlconf{"secondaryowner"}},
4096+ $mlconf{"primaryapprover"}, @{$mlconf{"secondarapprover"}})) {
4097+ # merging owners and approvers may result in a loop:
4098+ if (lc($admin) ne lc("owner-" . $mlconf{"name"} . "\@" . $mydomain)) {
4099+ $mmconf{"owner"} .= ",\'" . "$admin" . "\'";
4100+ }
4101+ }
4102+ $mmconf{"owner"} =~ s/^,//g;
4103+ $mmconf{"owner"} = "\[" . $mmconf{"owner"} . "\]";
4104+
4105+ # remove characters that will break Python
4106+ ($mmconf{"description"} = $mlconf{"description"}) =~ s/\'/\\\'/g;
4107+ $mmconf{"description"} = "\'" . $mmconf{"description"} . "\'";
4108+
4109+ $mmconf{"info"} = "\"\"\"\n" . $mlconf{"info"} . "\"\"\"\n";
4110+
4111+ $mmconf{"subject_prefix"} = "\'" . $mlconf{"subject_prefix"} . "\'";
4112+
4113+ $mmconf{"welcome_msg"} = "\"\"\"\n" . $mlconf{"intro"} . "\"\"\"\n";
4114+
4115+ # I don't know how to handle this because the reply_to param in the lists
4116+ # I had were not configured consistently
4117+ if ($mlconf{"reply_to"} =~ /\S+/) {
4118+ if ($mlconf{"name"} . "\@" =~ m/$mlconf{"reply_to"}/i) {
4119+ $mmconf{"reply_goes_to_list"} = "1";
4120+ $mmconf{"reply_to_address"} = "\'\'";
4121+ }
4122+ else {
4123+ $mmconf{"reply_goes_to_list"} = "2";
4124+ $mmconf{"reply_to_address"} = "\'" . $mlconf{"reply_to"} . "\'";
4125+ }
4126+ }
4127+ else {
4128+ $mmconf{"reply_goes_to_list"} = "0";
4129+ $mmconf{"reply_to_address"} = "\'\'";
4130+ }
4131+
4132+ $mmconf{"administrivia"} = ($mlconf{"administrivia"} =~ m/yes/i) ? "1" : "0";
4133+ $mmconf{"send_welcome_msg"} = ($mlconf{"welcome"} =~ m/yes/i) ? "1" : "0";
4134+
4135+ $mmconf{"max_message_size"} = int ($mlconf{"maxlength"} / 1000);
4136+
4137+ $mmconf{"host_name"} = ($mlconf{"resend_host"} =~ /\S+/) ?
4138+ $mlconf{"resend_host"} : "\'" . $mydomain . "\'";
4139+
4140+ $mmconf{"web_page_url"} = "\'" . $myurl . "\'";
4141+
4142+ # problematic since Mailman does not know access patterns
4143+ # I assume, that if there was given a noadvertise pattern, the
4144+ # list shouldn't be visible at all
4145+ $mmconf{"advertised"} = ($mlconf{"noadvertise"} =~ /\.\*/) ? "0" : "1";
4146+
4147+ # confirm+approval is much to long winded for private sites
4148+ $mmconf{"subscribe_policy"} =
4149+ ($mlconf{"subscribe_policy"} =~ m/(open|auto)/i) ? "1" :
4150+ ($private =~ m/yes/i) ? "2" : "3";
4151+
4152+ # in case this is a private site allow list visiblity at most
4153+ $mmconf{"private_roster"} =
4154+ ($mlconf{"who_access"} =~ m/open/i and not $private =~ m/yes/i) ? "0" :
4155+ ($mlconf{"who_access"} =~ m/open|list/i) ? "1" : "2";
4156+
4157+ $mmconf{"moderated"} = ($mlconf{"moderate"} =~ m/yes/i) ? "1" : "0";
4158+ # there is no way to a set a separate moderator in Mailman
4159+
4160+ # external, since lengthy
4161+ mm_posters();
4162+
4163+ if ($mlconf{"message_fronter"} =~ /\S+/) {
4164+ $mmconf{"msg_header"} = "\"\"\"\n" . $mlconf{"message_fronter"} . "\"\"\"\n";
4165+ }
4166+ else {
4167+ $mmconf{"msg_header"} = "\'\'";
4168+ }
4169+
4170+ if ($mlconf{"message_footer"} =~ /\S+/) {
4171+ $mmconf{"msg_footer"} = "\"\"\"\n" . $mlconf{"message_footer"} . "\"\"\"\n";
4172+ }
4173+ else {
4174+ $mmconf{"msg_footer"} = "\'\'";
4175+ }
4176+
4177+ # gateway to news
4178+ $mmconf{"nntp_host"} = "\'" . $newsserver . "\'";
4179+ $mmconf{"linked_newsgroup"} = "\'" . $newsprefix . $mlconf{"name"} . "\'";
4180+
4181+ if ($mlconf{"gated"} =~ m/yes/i) {
4182+ $mmconf{"gateway_to_news"} = "1";
4183+ $mmconf{"gateway_to_mail"} = "1";
4184+ $mmconf{"archive"} = "1";
4185+ }
4186+ else {
4187+ $mmconf{"gateway_to_news"} = "0";
4188+ $mmconf{"gateway_to_mail"} = "0";
4189+ $mmconf{"archive"} = "0";
4190+ }
4191+
4192+ # print warnings if this seems to be an umbrella list
4193+ for $elem (@{$mlconf{"privileged"}}, @{$mlconf{"members"}}) {
4194+ $elem =~ s/\@$mydomain//gi;
4195+ if (defined($mlaliases{$elem . $umbrella_member_suffix})) {
4196+ print STDERR "\"" . $mlconf{"name"} .
4197+ "\" possibly forms part off/is an umbrella list, since \"$elem\" is a local mailing list alias\n";
4198+ }
4199+ }
4200+
4201+ # print warnings if we encountered a Taboo-Header or Taboo-Body
4202+ if ($mlconf{"taboo_headers"} =~ /\S+/ or $mlconf{"taboo_body"} =~ /\S+/) {
4203+ print STDERR "\"" . $mlconf{"name"} . "\" taboo_headers or taboo_body seem to be set - please check manually.\n";
4204+ }
4205+}
4206+
4207+#
4208+# with some set theory on the member and priviliged list try to determine the params
4209+# $mmconf{"member_posting_only"} and $mmconf{"posters"}
4210+#
4211+
4212+sub mm_posters {
4213+ if ($mlconf{"restrict_post"} =~ /\S+/) {
4214+ my %privileged = ();
4215+ my %members = ();
4216+ my $key;
4217+
4218+ foreach $key (@{$mlconf{"privileged"}}) { $privileged{$key} = "OK"; }
4219+ foreach $key (@{$mlconf{"members"}}) { $members{$key} = "OK"; }
4220+
4221+ # are all members privileged, too ?
4222+ my $included = 1;
4223+ foreach $key (keys %members) {
4224+ if (not exists $privileged{$key}) {
4225+ $included = 0;
4226+ last;
4227+ }
4228+ }
4229+ if ($included) {
4230+ $mmconf{"member_posting_only"} = "1";
4231+
4232+ # posters = privileged - members:
4233+ my %diff = %privileged;
4234+ foreach $key (keys %members) {
4235+ delete $diff{$key} if exists $members{$key};
4236+ }
4237+
4238+ $mmconf{"posters"} = "";
4239+ for $key (sort keys %diff) {
4240+ $mmconf{"posters"} .= ",\'" . $key . "\'";
4241+ }
4242+ $mmconf{"posters"} =~ s/^,//g;
4243+ $mmconf{"posters"} = "[" . $mmconf{"posters"} . "]";
4244+ }
4245+ else {
4246+ $mmconf{"member_posting_only"} = "0";
4247+
4248+ # posters = privileged:
4249+ $mmconf{"posters"} = "";
4250+ for $key (sort keys %privileged) {
4251+ $mmconf{"posters"} .= ",\'" . $key . "\'";
4252+ }
4253+ $mmconf{"posters"} =~ s/^,//g;
4254+ $mmconf{"posters"} = "[" . $mmconf{"posters"} . "]";
4255+ }
4256+ }
4257+ else {
4258+ $mmconf{"member_posting_only"} = "0";
4259+ $mmconf{"posters"} = "[]";
4260+ }
4261+}
4262+
4263
4264=== added file 'contrib/mm-handler'
4265--- contrib/mm-handler 1970-01-01 00:00:00 +0000
4266+++ contrib/mm-handler 2010-10-14 12:15:59 +0000
4267@@ -0,0 +1,236 @@
4268+#!/usr/local/bin/perl
4269+##
4270+## Sendmail mailer for Mailman
4271+##
4272+## Simulates these aliases:
4273+##
4274+##testlist: "|/home/mailman/mail/mailman post testlist"
4275+##testlist-admin: "|/home/mailman/mail/mailman admin testlist"
4276+##testlist-bounces: "|/home/mailman/mail/mailman bounces testlist"
4277+##testlist-confirm: "|/home/mailman/mail/mailman confirm testlist"
4278+##testlist-join: "|/home/mailman/mail/mailman join testlist"
4279+##testlist-leave: "|/home/mailman/mail/mailman leave testlist"
4280+##testlist-owner: "|/home/mailman/mail/mailman owner testlist"
4281+##testlist-request: "|/home/mailman/mail/mailman request testlist"
4282+##testlist-subscribe: "|/home/mailman/mail/mailman subscribe testlist"
4283+##testlist-unsubscribe: "|/home/mailman/mail/mailman unsubscribe testlist"
4284+##owner-testlist: testlist-owner
4285+
4286+## Some assembly required.
4287+$MMWRAPPER = "/home/mailman/mail/mailman";
4288+$MMLISTDIR = "/home/mailman/lists";
4289+$SENDMAIL = "/usr/lib/sendmail -oem -oi";
4290+$VERSION = '$Id: mm-handler 5100 2002-04-05 19:41:09Z bwarsaw $';
4291+
4292+## Comment this if you offer local user addresses.
4293+$NOUSERS = "\nPersonal e-mail addresses are not offered by this server.";
4294+
4295+# uncomment for debugging....
4296+#$DEBUG = 1;
4297+
4298+use FileHandle;
4299+use Sys::Hostname;
4300+use Socket;
4301+
4302+($VERS_STR = $VERSION) =~ s/^\$\S+\s+(\S+),v\s+(\S+\s+\S+\s+\S+).*/\1 \2/;
4303+
4304+$BOUNDARY = sprintf("%08x-%d", time, time % $$);
4305+
4306+## Informative, non-standard rejection letter
4307+sub mail_error {
4308+ my ($in, $to, $list, $server, $reason) = @_;
4309+ my $sendmail;
4310+
4311+ if ($server && $server ne "") {
4312+ $servname = $server;
4313+ } else {
4314+ $servname = "This server";
4315+ $server = &get_ip_addr;
4316+ }
4317+
4318+ #$sendmail = new FileHandle ">/tmp/mm-$$";
4319+ $sendmail = new FileHandle "|$SENDMAIL $to";
4320+ if (!defined($sendmail)) {
4321+ print STDERR "$0: cannot exec \"$SENDMAIL\"\n";
4322+ exit (-1);
4323+ }
4324+
4325+ $sendmail->print ("From: MAILER-DAEMON\@$server
4326+To: $to
4327+Subject: Returned mail: List unknown
4328+Mime-Version: 1.0
4329+Content-type: multipart/mixed; boundary=\"$BOUNDARY\"
4330+Content-Disposition: inline
4331+
4332+--$BOUNDARY
4333+Content-Type: text/plain; charset=us-ascii
4334+Content-Description: Error processing your mail
4335+Content-Disposition: inline
4336+
4337+Your mail for $list could not be sent:
4338+ $reason
4339+
4340+For a list of publicly-advertised mailing lists hosted on this server,
4341+visit this URL:
4342+ http://$server/
4343+
4344+If this does not resolve your problem, you may write to:
4345+ postmaster\@$server
4346+or
4347+ mailman-owner\@$server
4348+
4349+
4350+$servname delivers e-mail to registered mailing lists
4351+and to the administrative addresses defined and required by IETF
4352+Request for Comments (RFC) 2142 [1].
4353+$NOUSERS
4354+
4355+The Internet Engineering Task Force [2] (IETF) oversees the development
4356+of open standards for the Internet community, including the protocols
4357+and formats employed by Internet mail systems.
4358+
4359+For your convenience, your original mail is attached.
4360+
4361+
4362+[1] Crocker, D. \"Mailbox Names for Common Services, Roles and
4363+ Functions\". http://www.ietf.org/rfc/rfc2142.txt
4364+
4365+[2] http://www.ietf.org/
4366+
4367+--$BOUNDARY
4368+Content-Type: message/rfc822
4369+Content-Description: Your undelivered mail
4370+Content-Disposition: attachment
4371+
4372+");
4373+
4374+ while ($_ = <$in>) {
4375+ $sendmail->print ($_);
4376+ }
4377+
4378+ $sendmail->print ("\n");
4379+ $sendmail->print ("--$BOUNDARY--\n");
4380+
4381+ close($sendmail);
4382+}
4383+
4384+## Get my IP address, in case my sendmail doesn't tell me my name.
4385+sub get_ip_addr {
4386+ my $host = hostname;
4387+ my $ip = gethostbyname($host);
4388+ return inet_ntoa($ip);
4389+}
4390+
4391+## Split an address into its base list name and the appropriate command
4392+## for the relevant function.
4393+sub split_addr {
4394+ my ($addr) = @_;
4395+ my ($list, $cmd);
4396+ my @validfields = qw(admin bounces confirm join leave owner request
4397+ subscribe unsubscribe);
4398+
4399+ if ($addr =~ /(.*)-(.*)\+.*$/) {
4400+ $list = $1;
4401+ $cmd = "$2";
4402+ } else {
4403+ $addr =~ /(.*)-(.*)$/;
4404+ $list = $1;
4405+ $cmd = $2;
4406+ }
4407+ if (grep /^$cmd$/, @validfields) {
4408+ if ($list eq "owner") {
4409+ $list = $cmd;
4410+ $cmd = "owner";
4411+ }
4412+ } else {
4413+ $list = $addr;
4414+ $cmd = "post";
4415+ }
4416+
4417+ return ($list, $cmd);
4418+}
4419+
4420+## The time, formatted as for an mbox's "From_" line.
4421+sub mboxdate {
4422+ my ($time) = @_;
4423+ my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
4424+ my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
4425+ my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
4426+ localtime($time);
4427+
4428+ ## Two-digit year handling complies with RFC 2822 (section 4.3),
4429+ ## with the addition that three-digit years are accommodated.
4430+ if ($year < 50) {
4431+ $year += 2000;
4432+ } elsif ($year < 1900) {
4433+ $year += 1900;
4434+ }
4435+
4436+ return sprintf ("%s %s %2d %02d:%02d:%02d %d",
4437+ $days[$wday], $months[$mon], $mday,
4438+ $hour, $min, $sec, $year);
4439+}
4440+
4441+BEGIN: {
4442+ $sender = undef;
4443+ $server = undef;
4444+ @to = ();
4445+ while ($#ARGV >= 0) {
4446+ if ($ARGV[0] eq "-r") {
4447+ $sender = $ARGV[1];
4448+ shift @ARGV;
4449+ } elsif (!defined($server)) {
4450+ $server = $ARGV[0];
4451+ } else {
4452+ push(@to, $ARGV[0]);
4453+ }
4454+ shift @ARGV;
4455+ }
4456+
4457+ if ($DEBUG) {
4458+ $to = join(',', @to);
4459+ print STDERR "to: $to\n";
4460+ print STDERR "sender: $sender\n";
4461+ print STDERR "server: $server\n";
4462+ exit(-1);
4463+ }
4464+
4465+ADDR: for $addr (@to) {
4466+ $prev = undef;
4467+ $list = $addr;
4468+
4469+ $cmd= "post";
4470+ if (! -f "$MMLISTDIR/$list/config.pck") {
4471+ ($list, $cmd) = &split_addr($list);
4472+ if (! -f "$MMLISTDIR/$list/config.pck") {
4473+ $was_to = $addr;
4474+ $was_to .= "\@$server" if ("$server" ne "");
4475+ mail_error(\*STDIN, $sender, $was_to, $server,
4476+ "no list named \"$list\" is known by $server");
4477+ next ADDR;
4478+ }
4479+ }
4480+
4481+ $wrapper = new FileHandle "|$MMWRAPPER $cmd $list";
4482+ if (!defined($wrapper)) {
4483+ ## Defer?
4484+ print STDERR "$0: cannot exec ",
4485+ "\"$MMWRAPPER $cmd $list\": deferring\n";
4486+ exit (-1);
4487+ }
4488+
4489+ # Don't need these without the "n" flag on the mailer def....
4490+ #$date = &mboxdate(time);
4491+ #$wrapper->print ("From $sender $date\n");
4492+
4493+ # ...because we use these instead.
4494+ $from_ = <STDIN>;
4495+ $wrapper->print ($from_);
4496+
4497+ $wrapper->print ("X-Mailman-Handler: $VERSION\n");
4498+ while (<STDIN>) {
4499+ $wrapper->print ($_);
4500+ }
4501+ close($wrapper);
4502+ }
4503+}
4504
4505=== added file 'contrib/mmdsr'
4506--- contrib/mmdsr 1970-01-01 00:00:00 +0000
4507+++ contrib/mmdsr 2010-10-14 12:15:59 +0000
4508@@ -0,0 +1,572 @@
4509+#!/bin/sh
4510+###############################################################################
4511+# mmdsr -- Mailman Daily Status Report (cron job, run at 23:59)
4512+###############################################################################
4513+# Copyright (c) 2005, Brad Knowles
4514+# All rights reserved.
4515+#
4516+# Redistribution and use in source and binary forms, with or without
4517+# modification, are permitted provided that the following conditions
4518+# are met:
4519+#
4520+# Redistributions of source code must retain the above copyright
4521+# notice, this list of conditions and the following disclaimer.
4522+#
4523+# Redistributions in binary form must reproduce the above copyright
4524+# notice, this list of conditions and the following disclaimer
4525+# in the documentation and/or other materials provided with the
4526+# distribution.
4527+#
4528+# The name of the author or other contributors may not be used
4529+# to endorse or promote products derived from this software without
4530+# specific prior written permission.
4531+#
4532+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4533+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
4534+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
4535+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
4536+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
4537+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
4538+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
4539+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
4540+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
4541+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
4542+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
4543+# POSSIBILITY OF SUCH DAMAGE.
4544+###############################################################################
4545+
4546+###############################################################################
4547+# Version history
4548+###############################################################################
4549+# 0.0.1 Initial creation by Brad Knowles <brad@stop.mail-abuse.org>
4550+# Created on: Tue Feb 15 04:01:20 PST 2005
4551+#
4552+# 0.0.2 Update by Brad Knowles <brad@stop.mail-abuse.org>
4553+# Updated on: Wed Feb 16 18:55:52 CET 2005
4554+# Feedback from Tom G. Christensen (tgc99):
4555+# > The current UID grab command doesn't work on Solaris
4556+# > (2.6 & 8 tested).
4557+# >
4558+# > I'd recommend this instead:
4559+# > ps -o user -p $$|tail -1
4560+# >
4561+# > This is tested and works on RH 6.2, RH 7.3, RHEL 2.1,
4562+# > RHEL3, FC3, FreeBSD 4.9, Solaris 2.6, 8.
4563+#
4564+# 0.0.3 Update by Brad Knowles <brad@stop.mail-abuse.org>
4565+# Updated on: Sun Mar 13 01:15:24 CET 2005
4566+# Noted errors from grep when trying to search a nonexistant
4567+# file. Added "-s" option to compensate, and extra code to
4568+# notify the admin if the log file doesn't exist.
4569+#
4570+# 0.0.4 Update by Brad Knowles <brad@stop.mail-abuse.org>
4571+# Updated on: Sun Mar 13 01:58:37 CET 2005
4572+# Eliminate some extra cruft from the "fromusenet" logs.
4573+#
4574+# 0.0.5 Update by Brad Knowles <brad@stop.mail-abuse.org>
4575+# Updated on: Wed Apr 13 00:54:42 CEST 2005
4576+# Eliminate a lot of extra cruft from the "error" & "vette" logs.
4577+# Eliminate more cruft from the "fromusenet" logs.
4578+#
4579+# 0.0.6 Update by Brad Knowles <brad@stop.mail-abuse.org>
4580+# Updated on: Thu Apr 14 03:07:08 CEST 2005
4581+# Eliminate even more cruft from the "fromusenet" logs.
4582+# Eliminate more cruft from the "vette" logs.
4583+# Eliminate cruft from the "mischief" logs.
4584+#
4585+# 0.0.7 Update by Brad Knowles <brad@stop.mail-abuse.org>
4586+# Updated on: Sun May 1 22:29:13 CEST 2005 [guess]
4587+# The "error" log "no such list" errors didn't need to be
4588+# quite so "compressed", open them up to be more readable.
4589+# Also eliminate all use of "xargs", use "fmt -w 75" instead.
4590+#
4591+# 0.0.8 Update by Brad Knowles <brad@stop.mail-abuse.org>
4592+# Updated on: Sun May 1 23:09:13 CEST 2005
4593+# Clean up code for queue subdirectories to check.
4594+#
4595+# 0.0.9 Update by Brad Knowles <brad@stop.mail-abuse.org>
4596+# Updated on: Wed May 11 01:23:40 CEST 2005
4597+# Eliminate date/time stamps from the rest of the logs,
4598+# then pipe through `sort | uniq -c | sort -nr`
4599+# so that all possible duplicates are eliminated.
4600+#
4601+# 0.0.10 Update by Brad Knowles <brad@stop.mail-abuse.org>
4602+# Updated on: Tue Jul 12 16:53:49 CDT 2005
4603+# Add code to do summary of normal list activity
4604+#
4605+# 0.0.11 Update by Brad Knowles <brad@stop.mail-abuse.org>
4606+# Updated on: Tue Sep 6 15:44:19 CEST 2005
4607+# Tweaked display of Mailman/qfiles subdirectories
4608+# so as to avoid displaying more than $MAX_LINES
4609+# output, which really helps if you have thousands
4610+# of bounces sitting around, etc....
4611+# Also slightly tweaked display of fromusenet logs
4612+# to split summary from errors.
4613+#
4614+# 0.0.12 Update by Brad Knowles <brad@stop.mail-abuse.org>
4615+# Updated on: Thu Sep 22 22:37:35 CEST 2005
4616+# Bugs found by Mark Sapiro and Adrian Wells.
4617+# Mark suggested splitting the Mailman log directory
4618+# from the rest of the Mailman source and queues,
4619+# as well as making sure to clean up all temp files.
4620+# Adrian discovered that there was a log file format
4621+# change between Mailman 2.1.5 and 2.1.6, which broke
4622+# hourly statistics.
4623+#
4624+# 0.0.13 Update by Brad Knowles <brad@stop.mail-abuse.org>
4625+# Updated on: Mon Dec 26 05:54:27 CET 2005
4626+# Bugs found by Tom G. Christensen (tgc99):
4627+# > ps output on solaris is full of whitespace but a further
4628+# > echo get's rid of it.
4629+# > The lines in the smtp log are sometimes broken up by a
4630+# > newline (right before the msgid) which throws of the
4631+# > summary. Piping it through sed first will rejoin the broken
4632+# > lines.
4633+# > Use $AWK instead of awk.
4634+# Enhancements from Mark Sapiro:
4635+# > The vette log summary lists posts held for moderation
4636+# > individually under "Other Errors:". The following patch
4637+# > (watch out for wrapped lines) summarizes them by list instead.
4638+#
4639+# 0.0.14 Update by Brad Knowles <brad@stop.mail-abuse.org>
4640+# Updated on: Thu Dec 29 08:17:38 CET 2005
4641+# Added code to check /usr/local/mailman/data for
4642+# moderation hold queue
4643+#
4644+# 0.0.15 Update by Brad Knowles <brad@stop.mail-abuse.org>
4645+# Updated on: Thu Jan 26 02:39:38 CET 2006
4646+# Tweaked display of summary data in the "smtp-failure" log,
4647+# and "other" category in the "vette" log, so as to reduce
4648+# the spewage when things go wonky, mostly by removing
4649+# unique-ifying data like message-id or sender address.
4650+# Also tweaked moderation queue information, to tell us
4651+# how many Python pickle files are in the data directory,
4652+# as opposed to just doing a directory listing and skipping
4653+# the files in the middle if there are too many -- now we
4654+# know how many are being skipped.
4655+#
4656+# 0.0.16 Update by Brad Knowles <brad@stop.mail-abuse.org>
4657+# Updated on: Sun Jan 29 11:45:58 CET 2006
4658+# Mark found a couple of typos that I somehow let slip
4659+# through. Thanks!
4660+
4661+###############################################################################
4662+# Set up locations of standard commands, directories, etc....
4663+# You may need to modify these for your platform or installation.
4664+###############################################################################
4665+
4666+AWK="/usr/bin/awk"
4667+BASENAME="/usr/bin/basename"
4668+CAT="/bin/cat"
4669+DATE="/bin/date"
4670+EGREP="/bin/egrep"
4671+FMT="/usr/bin/fmt"
4672+GREP="/bin/grep"
4673+HEAD="/usr/bin/head"
4674+LS="/bin/ls"
4675+PS="/bin/ps"
4676+RM="/bin/rm"
4677+SED="/bin/sed"
4678+SENDMAIL="/usr/sbin/sendmail"
4679+SLEEP="/bin/sleep"
4680+SORT="/usr/bin/sort"
4681+TAIL="/usr/bin/tail"
4682+TOUCH="/bin/touch"
4683+TR="/usr/bin/tr"
4684+UNIQ="/usr/bin/uniq"
4685+XARGS="/usr/bin/xargs"
4686+WC="/usr/bin/wc"
4687+
4688+###############################################################################
4689+# Directory for temporary files
4690+###############################################################################
4691+TMPDIR="/tmp"
4692+
4693+###############################################################################
4694+# Mailman queue directory
4695+###############################################################################
4696+QUEUEDIR="/usr/local/mailman/qfiles"
4697+
4698+###############################################################################
4699+# Mailman log directory
4700+###############################################################################
4701+LOGDIR="/usr/local/mailman/logs"
4702+
4703+###############################################################################
4704+# Mailman data directory
4705+###############################################################################
4706+DATADIR="/usr/local/mailman/data"
4707+
4708+###############################################################################
4709+# Maximum number of subdirectory entries to display in report
4710+###############################################################################
4711+MAX_QUEUE_LINES=20
4712+
4713+###############################################################################
4714+# Maximum number of moderation queue pickle files to display in report
4715+###############################################################################
4716+MAX_DATA_LINES=100
4717+
4718+###############################################################################
4719+# Mailman Log files to check for errors.
4720+# No need to specify path, only log file name.
4721+###############################################################################
4722+ERR_LOGS="error fromusenet locks mischief post qrunner smtp-failure vette"
4723+
4724+###############################################################################
4725+# Mailman Log files to summarize.
4726+# No need to specify path, only log file name.
4727+###############################################################################
4728+SUM_LOGS="fromusenet post smtp"
4729+
4730+###############################################################################
4731+# Mailman queue subdirectories to check. No need to specify path, only name.
4732+###############################################################################
4733+SUBDIRS="archive bounces commands in news out retry shunt virgin"
4734+
4735+###############################################################################
4736+# Specify recipients for report. If none, then simply print to "STDOUT".
4737+# Specify sender address for report. Not used if recipient list is empty.
4738+###############################################################################
4739+
4740+SENDER="INSERT.YOUR.SENDER.ADDRESS@HERE"
4741+RCPTS="INSERT.YOUR.RECIPIENTS.ADDRESSES@HERE"
4742+
4743+###############################################################################
4744+# If you run this program in cron at 23:59:00, you need to sleep sixty
4745+# seconds to make sure that you capture all the logs for the previous day.
4746+# Comment this line out to disable sleeping altogether.
4747+###############################################################################
4748+
4749+SLEEPTIME=60
4750+
4751+###############################################################################
4752+# What user id is Mailman installed under?
4753+# How do we determine what UID this program is running as?
4754+# NB: There will probably be SysV vs. BSD semantic differences here.
4755+# Be careful. Make sure the command you specify actually works.
4756+# The command string specified here may seem arcane, but should pull
4757+# out the information we need, and throw away everything else. If
4758+# there is an easier cross-platform way to do it, please let me know.
4759+###############################################################################
4760+
4761+GRABUID=`$PS -o user -p $$ | $TAIL -1`
4762+MYUID=`echo $GRABUID`
4763+RUNAS="mailman"
4764+
4765+###############################################################################
4766+# No modifications below this line should be required.
4767+###############################################################################
4768+
4769+DAY=`$DATE '+%b %d'`
4770+YEAR=`$DATE '+%Y'`
4771+
4772+PROG=`$BASENAME $0`
4773+
4774+if [ "${MYUID}" != "${RUNAS}" -a "${MYUID}" != "root" ] ; then
4775+ echo "ERROR: You must be root or ${RUNAS} to run $PROG."
4776+ exit 1
4777+fi
4778+
4779+if [ "${SLEEPTIME}" != "" -a "${RCPTS}x" != "${SENDER}x" ] ; then
4780+ if [ $SLEEPTIME -gt 0 ] ; then
4781+ $SLEEP $SLEEPTIME
4782+ fi
4783+fi
4784+
4785+TMP="$TMPDIR/.$PROG.$$"
4786+TMPLOG="$TMP.log"
4787+$RM -f $TMP
4788+$TOUCH $TMP
4789+
4790+if [ "${RCPTS}x" != "x" ] ; then
4791+ echo "From: ${SENDER}" >> $TMP
4792+ echo "To: ${RCPTS}" >> $TMP
4793+ echo "Subject: Mailman Daily Status Report: $DAY $YEAR" >> $TMP
4794+ echo "" >> $TMP
4795+fi
4796+
4797+echo " Mailman Daily Status Report: $DAY $YEAR" >> $TMP
4798+echo "" >> $TMP
4799+echo "" >> $TMP
4800+
4801+echo "******************************" >> $TMP
4802+echo "Log File Summary" >> $TMP
4803+echo "******************************" >> $TMP
4804+echo "" >> $TMP
4805+
4806+for LOG in $SUM_LOGS
4807+do
4808+
4809+ $RM -f $TMPLOG
4810+ $TOUCH $TMPLOG
4811+ echo "Log file: $LOG" >> $TMP
4812+ echo "==============================" >> $TMP
4813+
4814+ if [ -f "$LOGDIR/${LOG}" ] ; then
4815+ $SED -e :a -e '$!N;s/\n //;ta' -e 'P;D' $LOGDIR/$LOG | $GREP -si "^$DAY [0-9][0-9:]* $YEAR" >> $TMPLOG
4816+
4817+ if [ "${LOG}" = "post" ] ; then
4818+
4819+ echo "" >> $TMP
4820+ echo "Hourly Summary of Posts" >> $TMP
4821+ echo "-----------------------" >> $TMP
4822+
4823+ $SED -e 's/^[A-Z][a-z][a-z] *[0-9]* //' -e 's/:.*$//' $TMPLOG | $UNIQ -c | $SORT -n +1 | $AWK '{ printf( "%8d %02d:00-%02d:59\n", $1, $2, $2 ) }' >> $TMP
4824+
4825+ echo "" >> $TMP
4826+ echo "Post Count by List" >> $TMP
4827+ echo "------------------" >> $TMP
4828+
4829+ $SED -e 's/^.* post to //' -e 's/ .*$//' $TMPLOG | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4830+
4831+ echo "" >> $TMP
4832+ echo "Post Count by Sender" >> $TMP
4833+ echo "--------------------" >> $TMP
4834+
4835+ $SED -e 's/^.* from //' -e 's/,.*$//' $TMPLOG | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4836+
4837+ elif [ "${LOG}" = "fromusenet" ] ; then
4838+
4839+ GRPS=`$EGREP -vi '(watermark:|nothing new| posted to |: \[[0-9\.]*\])' $TMPLOG | $GREP -i ' gating ' | $AWK '{ print $7 }' | $SORT -u | $FMT -w 75`
4840+
4841+ for GRP in $GRPS
4842+ do
4843+ echo "" >> $TMP
4844+ echo "$GRP Article #'s Gated:" >> $TMP
4845+ echo "------------------------------" >> $TMP
4846+ $EGREP -vi '(watermark:|nothing new| posted to |: \[[0-9\.]*\])' $TMPLOG | $GREP -i " gating " | $GREP -i " $GRP " | $SED -e 's/^.*\[//' -e 's/\]$//' -e 's/\.\./ /' | $TR ' ' '\n' | $SORT -u | $FMT -w 75 >> $TMP
4847+ done
4848+
4849+ elif [ "${LOG}" = "smtp" ] ; then
4850+
4851+ echo "" >> $TMP
4852+ echo "Hourly Summary of Messages Sent" >> $TMP
4853+ echo "-------------------------------" >> $TMP
4854+ $SED -e 's/^[A-Z][a-z][a-z] *[0-9]* //' -e 's/:.* for / /' -e 's/ recips,.*$//' $TMPLOG | $AWK '{ val=int($1); sum[val]+=$2 } END { for (i=0; i<24; i++) { printf "%8d %02d:00-%02d:59\n", sum[i], i, i } }' >> $TMP
4855+
4856+ else
4857+
4858+ $SED 's/^.* ([0-9]*) //' $TMPLOG | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4859+
4860+ fi
4861+
4862+ else
4863+
4864+ echo " ### Log file ${LOG} does not exist ### " >> $TMP
4865+
4866+ fi
4867+
4868+ echo "" >> $TMP
4869+
4870+done
4871+
4872+echo "******************************" >> $TMP
4873+echo "Log File Squawks" >> $TMP
4874+echo "******************************" >> $TMP
4875+echo "" >> $TMP
4876+
4877+for LOG in $ERR_LOGS
4878+do
4879+ $RM -f $TMPLOG
4880+ $TOUCH $TMPLOG
4881+ echo "Log file: $LOG" >> $TMP
4882+ echo "==============================" >> $TMP
4883+ $GREP -si "^$DAY [0-9][0-9:]* $YEAR" $LOGDIR/$LOG >> $TMPLOG
4884+
4885+ if [ -f "$LOGDIR/${LOG}" ] ; then
4886+
4887+ if [ "${LOG}" = "error" ] ; then
4888+
4889+ echo "Uncaught Runner Exceptions:" >> $TMP
4890+ echo "------------------------------" >> $TMP
4891+ $GREP 'Uncaught runner exception' $TMPLOG | $SED 's/^.*exception: //' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4892+
4893+ echo "" >> $TMP
4894+ echo "No Such List:" >> $TMP
4895+ echo "------------------------------" >> $TMP
4896+ $GREP 'No such list' $TMPLOG | $SED -e 's/^.* "//' -e 's/".*$//' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4897+
4898+ CNT=`$GREP -i 'shunting' $TMPLOG | $WC -l`
4899+ if [ "${CNT}x" != "x" ] ; then
4900+ if [ ${CNT} -gt 0 ] ; then
4901+ echo "" >> $TMP
4902+ echo "Shunting Count: $CNT" >> $TMP
4903+ echo "------------------------------" >> $TMP
4904+ fi
4905+ fi
4906+
4907+ echo "" >> $TMP
4908+ echo "Other Errors:" >> $TMP
4909+ echo "------------------------------" >> $TMP
4910+ $EGREP -vi '(Uncaught runner exception|No such list|Ignoring unparseable message|Traceback|shunting)' $TMPLOG | $SED 's/^.* ([0-9]*) //' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4911+
4912+ elif [ "${LOG}" = "fromusenet" ] ; then
4913+
4914+ $EGREP -vi '(watermark:|nothing new| posted to |: \[[0-9\.]*\])' $TMPLOG | $GREP -vi " gating " | $SED 's/^.* ([0-9]*) //' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4915+
4916+ elif [ "${LOG}" = "mischief" ] ; then
4917+
4918+ echo "" >> $TMP
4919+ echo "Login failure with private rosters (by user):" >> $TMP
4920+ echo "------------------------------" >> $TMP
4921+ $GREP -i 'Login failure with private rosters' $TMPLOG | $AWK '{ print $NF }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4922+
4923+ echo "" >> $TMP
4924+ echo "Unsub attempt of non-member (by user):" >> $TMP
4925+ echo "------------------------------" >> $TMP
4926+ $GREP -i 'Unsub attempt of non-member' $TMPLOG | $AWK '{ print $NF }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4927+
4928+ echo "" >> $TMP
4929+ echo "Reminder attempt of non-member (by user):" >> $TMP
4930+ echo "------------------------------" >> $TMP
4931+ $GREP -i 'Reminder attempt of non-member' $TMPLOG | $AWK '{ print $NF }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4932+
4933+ echo "" >> $TMP
4934+ echo "Other Messages:" >> $TMP
4935+ echo "------------------------------" >> $TMP
4936+ $EGREP -vi '(Login failure with private rosters|Unsub attempt of non-member|Reminder attempt of non-member)' $TMPLOG | $SED 's/^.* ([0-9]*) //' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4937+
4938+ elif [ "${LOG}" = "post" ] ; then
4939+
4940+ $GREP -vi 'success' $TMPLOG | $SED 's/^.* ([0-9]*) //' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4941+
4942+ elif [ "${LOG}" = "vette" ] ; then
4943+
4944+ echo "" >> $TMP
4945+ echo "Message held -- Post by non-member (by list):" >> $TMP
4946+ echo "------------------------------" >> $TMP
4947+ $GREP 'Post by non-member' $TMPLOG | $AWK '{ print $6 }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4948+
4949+ echo "" >> $TMP
4950+ echo "Message held -- Suspicious header (by list):" >> $TMP
4951+ echo "------------------------------" >> $TMP
4952+ $GREP -i 'suspicious header' $TMPLOG | $AWK '{ print $6 }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4953+
4954+ echo "" >> $TMP
4955+ echo "Discarded posting (by list):" >> $TMP
4956+ echo "------------------------------" >> $TMP
4957+ $GREP -i 'Discarded posting' $TMPLOG | $AWK '{ print $6 }' | $SED 's/:$//' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4958+
4959+ echo "" >> $TMP
4960+ echo "Bulk/Junk message discarded (by list):" >> $TMP
4961+ echo "------------------------------" >> $TMP
4962+ $EGREP -i '(bulk|junk) message discarded' $TMPLOG | $AWK '{ print $NF }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4963+
4964+ echo "" >> $TMP
4965+ echo "Implicit destination (by list):" >> $TMP
4966+ echo "------------------------------" >> $TMP
4967+ $GREP -i 'Message has implicit destination' $TMPLOG | $AWK '{ print $6 }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4968+
4969+ echo "" >> $TMP
4970+ echo "Post to moderated newsgroup (by list):" >> $TMP
4971+ echo "------------------------------" >> $TMP
4972+ $GREP -i 'Posting to a moderated newsgroup' $TMPLOG | $AWK '{ print $6 }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4973+
4974+ echo "" >> $TMP
4975+ echo "Post to moderated list (by list):" >> $TMP
4976+ echo "------------------------------" >> $TMP
4977+ $GREP -i 'Post to moderated list' $TMPLOG | $AWK '{ print $6 }' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4978+
4979+ echo "" >> $TMP
4980+ echo "Other Errors:" >> $TMP
4981+ echo "------------------------------" >> $TMP
4982+ $EGREP -vi '(Post by non-member|suspicious header|message approved|Discarded posting|bulk message discarded|junk message discarded|Message has implicit destination|Posting to a moderated newsgroup|Post to moderated list|Message discarded, msgid)' $TMPLOG | $SED -e 's/^.* ([0-9]*) //' -e 's/, message-id=<[^> ]*>:/:/' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4983+
4984+ elif [ "${LOG}" = "smtp-failure" ] ; then
4985+
4986+ $SED 's/^.* ([0-9]*) //' $TMPLOG | $SED 's/delivery to [^@ ]*@[^@ ]* failed with code/delivery failed with code/g' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4987+
4988+ else
4989+
4990+ $SED 's/^.* ([0-9]*) //' $TMPLOG | $SORT | $UNIQ -c | $SORT -nr >> $TMP
4991+
4992+ fi
4993+ else
4994+
4995+ echo " ### Log file ${LOG} does not exist ### " >> $TMP
4996+
4997+ fi
4998+ echo "" >> $TMP
4999+
5000+done
The diff has been truncated for viewing.