#### #### PPML ver.0.753 --- "Pigeon Post" Mail-list System #### Copyright (c) 1999-2003 by Kazunori ANDO all rights reserved. #### #### http://www.kk.iij4u.or.jp/~ando/ppml/README #### #### This program is free software; you can redistribute it and/or #### modify it under the terms of the GNU General Public License #### as published by the Free Software Foundation; either version 2 #### of the License, or (at your option) any later version. #### #### This program is distributed in the hope that it will be useful, #### but WITHOUT ANY WARRANTY; without even the implied warranty of #### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #### GNU General Public License for more details. #### #### You should have received a copy of the GNU General Public License #### along with this program; if not, write to the Free Software #### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #### 02111-1307, USA. ####
とだけ書けば良いようにデフォルト設定をppml.plに搭載sub MakeHeader { &DefaultMakeHeader(@_); }
民田さん@松井証券、さとる氏@とんきんぐらでゅえーとほか、選りすぐり の数名の方たちありがとう :)
著作権は安藤一憲(ando@kk.iij4u.or.jp)が保持しています。 本ソフトウェアのライセンスはGNU Public License(version 2)に準拠します。 詳しくはCOPYINGファイルをごらん下さい。 本ソフトのパッケージにはmime_plsとDigest-MD5-2.07の全パッケージ が含まれています。
このシステムの"Pigeon Post"(伝書鳩通信)の名前はシンプルで軽いという イメージからIRCの某チャンネルで民田さん(minmin@wide.ad.jp)によって 名付けられました。
「読めないMLドライバはもういらない」のコンセプトのもと、余分なぜい 肉はそぎ落して、安全性に配慮して基本機能にこだわりつつ見通しの良い プログラムを目指しました。もちろんすべての部分をスクラッチから書い ています。コマンドインターフェースはmajordomo風味ですが完全に同じで はありません。若干のperlの知識があればかなりの自由度で各MLを設定で きます。わけのわからない変数で設定するよりはパーサの仕様を公開して ダイレクトにヘッダ等をいじるほうが中身が見える分安心でしょう。 従来のMLドライバと比較すると、ppmlはRFC822的メッセージ(メール)を perlでハンドリングするための基礎インターフェースと言った方が近い かもしれませんが、もちろんMLドライバとしても高性能です。
他の多くのMLドライバと決定的に違うことに、メンバーリストと管理者リス トが.phファイルになっており、リストはその中でhashとして定義されてい ます。これは、投稿者認証等の線形サーチ等による無駄と処理プログラムの 複雑化を防ぎ、同じアドレスの重複登録が構造的に発生しないという大きな 特徴を持っています。hashのdataの部分は、管理者リストではパスワード、 メンバーリストではGECOSの収納場所として使われており、whoコマンドに よるアドレスの一括漏洩の阻止に一役買っています。
ループ/bombing対策として、Message-Idとメールの本文部分のchecksumの ダブルチェックを行なっています。従って本文部分が全く同一のメールは pp.cacheがクリアされない限り2度と投稿できません(短いメールの多いML では1日1回くらいcronでクリアしたほうが良いかも)。また、log機能が 充実しておりSMTPで発信したメールの宛先アドレス数と終了ステータスは 全てlogに残ります。
バイナリ貼付型ウイルスへの対策としてdigital-signatureによる ウイルスチェックルーチンを搭載しました。手軽にマルチパートメール の各部分のdigital-signatureが取れるようにsigcheck.plコマンドも追加 してあります。ウイルスと判明した場合は次のようにして出力結果を お知らせ下さい。ppvirus.phに追加してリリースさせて頂きます。 (ただし、現状のところマクロウイルスには効果がありません)
% sigcheck.pl < 22 ^^ ウイルス入りと判明したファイル
0.57で湯川さん作のIPv6対応パッチを収録しています。パッチ適用のしかたは ppmlのソースディレクトリにて、
% patch -pl < patches/ppml-0.562-patch-yugawa-ipv6.txtです。
0.603でOPTIMIZED_DELIVERLIST機能を追加しました。これは、 『複数ML宛にポストされたメールが各メンバーに重複して配送されるのを防ぐ』 という目的で設計されています。メンバーが重複しがちなMLでメンバーリストを 共用することでこの機能を実現しています。この機能を利用するには重複配送を 阻止したい各MLのconfig.phで、
### Optimized $OPTIMIZED_DELIVERLIST = 1; @MEMBERLIST = ('../opt-members.ph','admins.ph'); @DELIVERLIST = ('../opt-members.ph');
のように指定します。この例では各MLで共用する opt-members.ph は MLの設定ディレクトリの外($ML_SETTING_ROOT直下)に置いています。 メンバーファイルの中身は、
%members = ( 'user1@ppml.tv' => ['Kazunori ANDO','info','# staff','support',], 'user2@hitokai.com' => ['Kazunori ANDO','info','support',], 'user3@kk.iij4u.or.jp' => ['Kazunori ANDO','support','staff',], ); 1;
のようになっており、To及びCcヘッダに含まれるアドレスのうち、 メンバーリストで最も先(すなわち左)に書いてあるMLにだけメールが 配送されます。同じメールが何通も来る環境でお悩みの場合にお試し 下さい。コマンドメールもほぼそのまま使用できますが、この機能を 実現する上で、登録・削除・on・off等のコマンドが@DELIVERLISTの 先頭にあるリストにしか作用しなくなっているので注意しましょう。 ML名が他のaliasで指定される場合には別名の登録ができます。
0.605ではML以外のアドレスがTo及びCcヘッダに含まれている場合に MLでそのアドレスへの配送が発生してもSKIPするOPTIMIZED_SKIP機構が 追加されました。
0.70でOPTIMIZED_DELIVERLIST用CGI群をパッケージに追加しました。 詳しくはcontrib/READMEを御覧下さい。
0.740で小山さん作のperlのDBIを用いて設定ファイルのいくつかを PostgreSQL等のDB上に乗せてしまうことのできるppmldb patchをパッ ケージに追加しました。内容に関してはREADME-dbaccessを御覧下さい。 パッチの適用のしかたは、
% zcat patches/ppml-0.740-patch-koyama-ppmldb.gz | patch -plです。
0.752でppmilter.plをパッケージに追加しました。 Sendmail::Milterモジュールをインストールして御利用下さい。
perl5.005_05とsendmail-8.12.9でテストしながら作成しています。 あと、どこかにSMTPでメールを受けられる(relayしてくれる)ホスト (localhostでかまわない)あるいはコマンドラインで動くsendmail等が 必要になります。sendmail-8.12.9の設定については、
http://www.kk.iij4u.or.jp/~ando/config.txt
に簡単な文書を準備するつもりです(^^;
##### main ##### &ReadSiteConfig; # sitedef.phを読む &ChdirByArg; # includeで指定したppml.plの引数のディレクトリにchdir &PPLockWait; # 排他的Lock &ReadConfig; # config.phを読む &ParseInput(*e); # メールを読んで%eに値を入れる &CheckCache(*e); # checksumとMessage-Idのcacheをチェック(ループ対策) &AuthPoster(*e); # 投稿者認証 &IncrSeqNum(*e); # シーケンスナンバの生成 &MIMEdecode(*e); # ヘッダのMIME decode &MakeHeader(*e); # ヘッダ加工(config.phで定義) #&DebugPrint(*e); &MIMEencode(*e); # ヘッダのMIME encode &SaveAsFile(*e) unless ($NOARCHIVE); # 送信されるメールを$Savedirにアーカイブ &Distribute(*e); # SMTPでメールを配る &Unlock; # 排他的lockの解除 &Done; ################
メール → $e{'Body'} → $e{'checksum:body'}, $e{'size:body'}, $e{'line:body'}, ※ 以下はMIME multipartなメッセージの場合 $e{'mp:00'}, # パート数 $e{'mp:00:boundary'} # バウンダリ $e{'mp:00.01'}, # 1つめのパート $e{'mhead:mp:00.01'} # 1つめのパートのMIMEヘッダ $e{'mbody:mp:00.01'} # 1つめのパートの本文 $e{'size:mp:00.01'} # 1つめのパートのサイズ $e{'checksum:mp:00.01'} # 1つめのdigital-signature $e{'mtype:mp:00.01'} # 1つめのパートのMIME-type $e{'mchar:mp:00.01'} # 1つめのパートのcharset $e{'mcode:mp:00.01'} # 1つめのパートのencoding $e{'mname:mp:00.01'} # 1つめのパートのfile名 $e{'mp:00.02'}, # 2つめのパート $e{'mhead:mp:00.02'} # 2つめのパートのMIMEヘッダ $e{'mbody:mp:00.02'} # 2つめのパートの本文 $e{'size:mp:00.02'}, # 2つめのパートのサイズ $e{'checksum:mp:00.02'}# 2つめのdigital-signature $e{'mtype:mp:00.02'} # 2つめのパートのMIME-type $e{'mchar:mp:00.02'} # 2つめのパートのcharset $e{'mcode:mp:00.02'} # 2つめのパートのencoding $e{'mname:mp:00.02'} # 2つめのパートのfile名 … (以下は2つめのパートがmultipartだった場合の解析) $e{'mp:02'} # パート数 $e{'mp:02:boundary'} # バウンダリ $e{'mp:02.01'}, # 1つめのパート $e{'mhead:mp:02.01'} # 1つめのパートのMIMEヘッダ $e{'mbody:mp:02.01'} # 1つめのパートの本文 $e{'size:mp:02.01'}, # 1つめのパートのサイズ $e{'checksum:mp:02.01'}# 1つめのdigital-signature $e{'mtype:mp:02.01'} # 1つめのパートのMIME-type $e{'mchar:mp:02.01'} # 1つめのパートのcharset $e{'mcode:mp:02.01'} # 1つめのパートのencoding $e{'mname:mp:02.01'} # 1つめのパートのfile名 … $e{'Header'} → $e{'size:header'} $e{'OH:From'}, # ヘッダの左辺(field-name) $e{'H:From'}, # ヘッダの右辺 $e{'OH:Subject'}, # ヘッダの左辺(field-name) $e{'H:Subject'}, # ヘッダの右辺 $e{'OH:Message-Id'}, # ヘッダの左辺(field-name) $e{'H:Message-Id'}, # ヘッダの右辺 … $e{'H:Received'} → $e{'received:0'}, # ヘッダの右辺のみ(差出し元) $e{'received:1'}, # ヘッダの右辺のみ(リレー1) $e{'received:2'}, # ヘッダの右辺のみ(リレー2) … ※ %e 中でkeyは「field-nameの先頭、'-'の後を大文字にしたもの」を使用 ※ %e 中でヘッダはkeyの頭の'H:'で識別
rootになってppmlを動作させるユーザを決めるor作成し、sendmail.cfの trusted userに加える(user:group = ppml:ppmlとする)
● /etc/sendmail.cf
# this is equivalent to setting class "t" T root daemon uucp ppml
##################### SET VARIABLES HERE #################### UID = ppml GID = ppml DOMAIN = ppml.tv FQDN = ns0.ppml.tv PERL = /usr/local/bin/perl PPMLDIR = /usr/local/ppml ML_SETTING_ROOT = /usr/local/lib/ppml #### CHDIR = cd #### for non-POSIX shells # CHDIR = chdir #############################################################
Digest-MD5-2.07のmoduleとppmlパッケージと設定例(skel)がインストールされる
この機能はlynxとnkfに依存しているので、あらかじめREADMEをパッケージに入れてあります:)。
● /usr/local/ppml/sitedef.ph
$Logfile = "pp.log"; $Savedir = "archive"; $Seqfile = "pp.seq"; $Cache = "pp.cache"; $PPEHLOG = "ppeh.log"; $ERRORLOG = "pp.err"; # エラーメール解析記録ファイル # $USE_LOCKFILE = 1; # 設定の選択肢は以下の通り。 # 指定なし:flock(config.ph) 1:linkを使ったlock(lockfile) # lockfileを使う方式の場合は、/etc/rc などに、 # rm -f $ML_SETTING_ROOT/*/archive/.lockfile # と入れておくと不意にOSが落ちたようなケースでも安心です。 $CHKSUMTYPE = 'SHA1'; # SHA1(160bit)とMD5(128bit)と指定なし(32bit)が選択可 # $ALIASES = "/etc/mail/aliases"; # $AUTOGENERATE = 1; # この2つを設定するとgenerateコマンドが使えるようになります。 # /etc/aliases に設定を書いて、generate# するだけで新規MLの設定ができます。 $ML_SETTING_ROOT = "/usr/local/lib/ppml"; # ML設定ディレクトリのroot $ML_CONTROL = 'ppserv@ppml.tv'; # コマンド受付アドレス $ML_SERVER_ADMIN = 'ppserv-request@ppml.tv'; # サーバ管理者アドレス $PPML_HOST = 'ns0.ppml.tv'; # PPML設置マシンFQDN $SMTP_HOST = 'localhost:25'; # メール発信先(必要) $SESSIONMAXRCPTS = 100; # SMTP1接続あたりの上限受信者数 $MIMEANALYZE = 1; # MIMEマルチパート解析オプション $MAXBODYSIZE = 500000; # 配布本文上限サイズ $MAXPARTSIZE = 200000; # 配布1パート上限サイズ # $SENDMAILを設定してあると配送にコマンドラインのsendmailを利用 # $SENDMAIL = '/usr/sbin/sendmail'; # IgnoreDotがsendmail.cfでTrueに設定されていない場合は以下のように指定 # $SENDMAIL = '/usr/sbin/sendmail -i'; # ユーザコマンドの抑止設定(0にするとそのコマンド使用不可) %USERCOMMPERM = ( 'index', '1', 'get', '1', 'on', '1', 'off', '1', 'unsubscribe', '1', 'who', '1', 'subscribe', '1', 'confirm', '1', 'info', '1', 'help', '1', ); # ヘッダ加工のデフォルト定義。 sub DefaultMakeHeader{ $0 = "MakeHeader"; local(*e) = @_; ### if you don't allow a field-name like "suBJeCt", # foreach $kk (keys(%e)){ # if ($kk =~ /^OH\:/){ # delete $e{$kk}; # } # } ### delete text/html part(sample:) # for ($b = 0 ; $b <= $e{'mp:00'} ; $b++){ # $mcb = sprintf("mp:%02d",$b); # for ($i = 1 ; $i <= $e{$mcb} ; $i++){ # $mcs = sprintf("mp:%02d.%02d",$b,$i); # if ($e{"mtype:$mcs"} =~ /text\/html/i){ # &DeletePart(*e,$b,$i); # } # } # } ### delete files based on extentions from attachment part(sample:) # for ($b = 0 ; $b <= $e{'mp:00'} ; $b++){ # $mcb = sprintf("mp:%02d",$b); # for ($i = 1 ; $i <= $e{$mcb} ; $i++){ # $mcs = sprintf("mp:%02d.%02d",$b,$i); # if (($e{"mname:$mcs"} =~ /(\.ade|\.adp|\.bas|\.bat|\.chm|\.cmd)/i)|| # ($e{"mname:$mcs"} =~ /(\.com|\.cpl|\.crt|\.css|\.exe|\.hlp)/i)|| # ($e{"mname:$mcs"} =~ /(\.hta|\.inf|\.ins|\.isp|\.js|\.jse)/i)|| # ($e{"mname:$mcs"} =~ /(\.lnk|\.mdb|\.mde|\.msc|\.msi|\.msp)/i)|| # ($e{"mname:$mcs"} =~ /(\.mst|\.pcd|\.pif|\.reg|\.scr|\.sct)/i)|| # ($e{"mname:$mcs"} =~ /(\.shs|\.url|\.vb|\.vbe|\.vbs|\.wsc)/i)|| # ($e{"mname:$mcs"} =~ /(\.wsf|\.wsh|\.csp|\.cab|\.pnf|\.ida)/i)){ # &DeletePart(*e,$b,$i); # } # } # } ### against BubbleBoy virus if (($e{'H:Subject'} =~ /BubbleBoy is back\!/i ) && ($e{'Body'} =~ /http\:\/\/www\.towns\.com\/dorms\/tom\/bblboy\.htm/i )){ &Log("$0:VIRUS: BubbleBoy detected"); &NoticeMail($ML_ADMIN,"BubbleBoy detected($BRACKET)",$e{'Header'}.$e{'Body'},*r); &NoticeMail($ML_SERVER_ADMIN,"BubbleBoy detected($BRACKET)",$e{'Header'}.$e{'Body'},*r); exit; } ### against 1-line Command posting if (defined($e{'seq'}) && ($e{'line:body'} <= 2)){ if ($e{'Body'} =~ /^\s*(info|on|off|who|help|subscribe|unsubscribe)\s*\S*/i){ &Log("$0:COMM: Command from $e{'poster'} rejected"); &NoticeMail($e{'poster'},"Command rejected($BRACKET)",$e{'Header'}.$e{'Body'},*r); &DecrSeqNum(*e); exit; } } ### delete header delete $e{'H:Received'}; delete $e{'H:Return-Path'}; delete $e{'H:X-Authentication-Warning'}; delete $e{'H:Return-Receipt-To'}; # to avoid DSN storm delete $e{'H:Disposition-Notification-To'}; # to avoid MDN storm ### change header $e{'H:Subject'} =~ s/\n//; $e{'H:Subject'} =~ s/\[$BRACKET\s+\d+\]//; 1 while $e{'H:Subject'} =~ s/\s*Re\:\s*Re\:\s*/Re\: /i ; $e{'H:Subject'} = "[$BRACKET $e{'seq'}] ".$e{'H:Subject'}."\n"; $e{'H:Subject'} =~ s/(\s)+/$1/g; $e{'H:Reply-To'} = $e{'H:Reply-To'} || "$ML_ADDR\n"; ### overwrite header $e{'H:Sender'} = "$ML_ADMIN\n"; $e{'H:Precedence'} = "bulk\n"; $e{'H:X-Sequense'} = "$BRACKET $e{'seq'}\n"; $e{'OH:X-Ml-System'} = "X-ML-System"; $e{'H:X-Ml-System'} = "Pigeon Post ver.0.753\n"; return 1; } 1; ● /usr/local/ppml/include
"|/usr/local/ppml/ppserv.pl "● /etc/aliases
# PPML Command Interface ppserv: :include:/usr/local/ppml/include ppserv-request: ando@ppml.tv # ML Setting skel: :include:/usr/local/lib/ppml/skel/include skel-request: ando@ppml.tv # newaliases を忘れずに
● /usr/local/lib/ppml/skel/include
"|/usr/local/ppml/ppml.pl /usr/local/lib/ppml/skel "※ ppml.plの引数にそのMLの設定ディレクトリを指定
● /usr/local/lib/ppml/skel/admins.ph
# 管理者アドレスと管理パスワードを設定 %members = ( 'ando@ppml.tv','PASSWORD', 'ando@kk.iij4u.or.jp','PASSWORD', ); 1;
● /usr/local/lib/ppml/skel/config.ph
# # ppml configuration file -- config.ph # @ADMINLIST = ('admins.ph'); # 管理者ファイル # @MEMBERLIST設定をコメントアウトすると誰でも投稿できるモード ### ノーマルな配送 # $OPTIMIZED_DELIVERLIST = 0; # @MEMBERLIST = ('admins.ph','members.ph'); # 投稿認証用ファイル # @DELIVERLIST = ('members.ph'); # メール配布先ファイル ### リスト共用で最適化配送 $OPTIMIZED_DELIVERLIST = 1; $OPTIMIZED_SKIP = 1; @MEMBERLIST = ('../opt-members.ph','admins.ph'); @DELIVERLIST = ('../opt-members.ph'); @H_ATOM = ('Subject','From','To','Cc','Date','Reply-To','Sender'); $BRACKET = 'PP-GENUINE'; # [PP-GENUINE 119] $ML_ADMIN = 'skel-request@ppml.tv'; # 管理者アドレス $ML_ADDR = 'skel@ppml.tv'; # MLのアドレス %MLALIAS = ( 'alias-of-skel' => 'skel', # MLの別名登録 ); # $AUTOSUBSCRIBE = 1; # 設定すると自動登録 # $NOARCHIVE = 1; # 設定するとアーカイブなし # ヘッダ加工自由自在 :p sub MakeHeader{ $0 = "MakeHeader"; local(*e) = @_; &DefaultMakeHeader(*e); ### delete text/html part(sample:) # for ($i = 1 ; $i <= $e{'mp'} ; $i++){ # if ($e{"mp:$i"} =~ /Content-Type:\s*text\/html/){ # &DeletePart(*e,$i); # } # } ### change Reply-To with AuthPoster ### これ使う時は sitedef.ph の関係行は削除のこと # if ($e{'auth'}){ # $e{'H:Reply-To'} = $e{'H:Reply-To'} || "$ML_ADDR\n"; # }else{ # $e{'H:Reply-To'} = "$ML_ADDR\n"; # } ### add footer(sample:) # $e{'Body'} .= "\n\n-- This is footer. Thanks!\n"; return 1; } 1;
● /etc/aliases
# ML Setting newml: :include:/usr/local/lib/ppml/newml/include newml-request: ando@ppml.tv # newaliases を忘れずに
コマンド受け付けアドレス(ppserv)宛に
generate newml initpasswdとコマンドを送ってMLを生成します。
approve PASSWORD subscribe MLNAME GECOS-ADDRESS ex. approve Der.S subscribe ppmltest あんどー@謎 <ando@kk.iij4u.or.jp> ex. approve Des.S subscribe ppmltest ando@kk.iij4u.or.jp (Kazunori ANDO) ex. approve Dem.S subscribe ppmltest ando@kk.iij4u.or.jp ※ GECOSはメンバーリスト(members.ph)に反映されます。 approve PASSWORD unsubscribe MLNAME ADDRESS approve PASSWORD off MLNAME ADDRESS approve PASSWORD on MLNAME ADDRESS approve PASSWORD passwd MLNAME NEWPASSWORD ※ パスワードはadmin1人1人で違うものに設定できます。 approve PASSWORD newinfo MLNAME ※ approve newinfoコマンド行の後の行にinfoの本文を書いて下さい。 approve PASSWORD welcome MLNAME ※ approve welcomeコマンド行の後の行にwelcomeの本文を書いて下さい。 ※ welcomeファイルを設置するとアドレスを登録した時にwelcomeメール が発信されるようになります。 approve PASSWORD who MLNAME ※ アドレス一覧の出るwhoはこちらです。 approve PASSWORD help MLNAME ※ 認証にはMLNAMEが必要なんです approve PASSWORD delete MLNAME 番号 ex. approve Den.S delete ppmltest 1-3,5,8,9 ex. approve Die.S delete ppmltest 1-4 6 10 approve PASSWORD get MLNAME 番号 ex. approve Der.S get ppmltest 1-3,5,8,9 ex. approve Der.S get ppmltest 1-4 6 10
get MLNAME 番号 ex. get ppmltest 1-3,6,7,8 ex. get ppmltest 2-6 9 10 index MLNAME who MLNAME ※ GECOSの一覧だけでメールアドレスは表示されません。 unsubscribe MLNAME off MLNAME on MLNAME
info MLNAME subscribe MLNAME confirm AUTH-NUMBER MLNAME ex. confirm 931067337-00000001893 ppmltest ※ AUTH-NUMBERはsubscribeするとサーバから返信されてきます。 generate MLNAME PASSWORD ※ /etc/mail/aliasesの設定を読んでskelをもとにMLの設定ディレクトリを生成します。 ※ 管理者の初期パスワードを指定します。 help