個別機能のmilterを一掃してRspamdを活用

これまでmilterにまみれていたpostfixだが、Rspamdを1.9.0に更新したついでにRspamdにある機能はRspamdに移すことにした。

この記事のファイルのPathはFreeBSDのシステムやpkg/portsパッケージに倣っている。

ウイルス検知 ClamAV

これまでclamav-milterを使用していたが、RspamdでもClamAVと連携できるのでそちらを使うことにした。

/usr/local/etc/rspamd/local.d/antivirus.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#enabled = false;     #無効ならこれだった。この行はもう要らない。

clamav {
    action = "reject";
    message = '${SCANNER}: virus found: "${VIRUS}"';
    log_clean = true;
    scan_mime_parts = true;      #旧verのattachments_onlyの代わり
    max_size = 256000;
    servers = "/var/run/clamav/clamd.sock";
}

DKIM (受信側)

/usr/local/etc/rspamd/modules.d/dkim.confの初期値から変える必要がなければ何もしなくてよい。

DKIM (送信側 DKIM署名)

DKIM署名を作る
# mkdir /var/db/rspamd/dkim
# rspamadm dkim_keygen -d example.com -s 201903 -b 2048 -k /var/db/rspamd/dkim/example.com.201903.key > /var/db/rspamd/dkim/example.com.201903.pub

上の例の201903の部分はセレクタで任意の文字列を指定する。この例では毎月変更する運用を予定していて2019年3月用という意味のつもり。 これで、/var/db/rspamd/dkimにexample.com.201903.pubとexample.com.201903.keyの2つのファイルが作成される。 Rspamdに登録するのはexample.com.201903.keyの方。
example.com.201903.pubの内容をDNSのexample.com用ゾーンファイルに書く。DNSがBINDならそのままコピペでOK.もちろん、気に入る書き方に変更するのも可。


/usr/local/etc/rspamd/local.d/dkim_signing.conf (新規)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
domain {
    example.com {
        selector = "201903";
        path = "/var/db/rspamd/dkim/example.com.201903.key";
    }
}
allow_hdrfrom_mismatch = true;
allow_hdrfrom_mismatch_sign_networks = true;
allow_username_mismatch = true;
use_domain = "header";
auth_only = true;
use_esld = true;
sign_local = true;
use_redis = false;

DMARC

DMARCはDNSで宣言するのでDNSへの設定追加が必要。Rspamdの方は受信したときの指定とレポート送信を設定する。

/usr/local/etc/rspamd/local.d/dmarc.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
reporting = true;
actions = {
    quarantine = "add_header";
    reject = "reject";
}

send_reports = true;
report_settings {
    org_name = "EXAMPLE.COM";
    domain = "example.com";
    email = "postmaster@example.com";
}

こんな感じ。

関連記事:
いまさらだけど遅すぎというほどでもないDMARC 導入編
いまさらだけど遅すぎというほどでもないDMARC レポート編1

メールヘッダの情報表示追加

Rspamdは受信したメールにメールのスパム判定について記載してくれるが、その設定。
特に「送信ドメイン認証」関係は初期値では非表示(または判りにくい)なので「導入したけど機能しているのかしら?」にならないよう表示設定する。逆に、迷惑メールの判定根拠を含めてこの手の結果は利用者には見せないという方針なら非表示にするのもアリ。

/etc/rspamd/local.d/milter_headers.conf
1
2
3
4
extended_spam_headers = true;
skip_local = true;
authenticated_headers = ["authentication-results"];
use = ["authentication-results", "x-spam-status"];

こんな感じ。1,2行目はこの記事的にはどうでも良い。しかも2行目は初期値なので書く必要すらない。4行目のx-spam-statusもこの記事的には不要だが、書き方の例として。

Postfixの設定変更

/usr/local/etc/postfix/main.cf (Milter部分 変更前)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#Milter
milter_default_action = accept
milter_mail_macros =  i {mail_addr} {client_addr} {client_name} {auth_authen}
milter_protocol = 6

smtpd_milters =
    unix:/var/run/opendkim/dkim.sock      #DKIM
    unix:/var/run/opendmarc/dmarc.sock    #DMARC
    unix:/var/run/clamav/clmilter.sock    #ClamAV
    unix:/var/run/rspamd/rspamd.sock      #Rspamd

non_smtpd_milters =
    unix:/var/run/opendkim/dkim.sock
    unix:/var/run/opendmarc/dmarc.sock
    unix:/var/run/clamav/clmilter.sock
    unix:/var/run/rspamd/rspamd.sock

/usr/local/etc/postfix/main.cf (Milter部分 変更後)
1
2
3
4
5
6
#Milter
milter_default_action = accept
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
milter_protocol = 6
smtpd_milters = unix:/var/run/rspamd/rspamd.sock
non_smtpd_milters = unix:/var/run/rspamd/rspamd.sock

基本的にはmain.cfの変更だが、master.cfに関連する設定を書いている場合はそれも忘れずに削除する。個人的なことだが、今回ClamAVでもDKIMでもDMARCでもない以前に使っていたmilterの関連設定が残っていたためにDKIM署名が機能しなくて非常に苦労した。(smtpd_milters= というのが残っていた)

サービスの停止・再起動・再読込

# service clamav-milter stop   #ClamAV-milterは停める
# service dkimproxy_out stop   #DKIM署名用milterは停める
# service milter-opendkim stop #DKIM受信判定用Milterは停める
# service opendmarc stop       #DMARCのmilterは停める
# service postfix reload       #Postfix設定再読込 またはrestartで
# service rspamd restart       #Rspamd再起動

届いたメールのヘッダを確認する

Authentication-Results: mx.example.com;
dkim=pass header.d=example.net header.s=abc12345 header.b=vwxyz;
dmarc=pass (policy=none) header.from=example.net;
spf=pass (mx.example.com: domain of hoge@example.net designates 192.168.0.250 as permitted sender) smtp.mailfrom=hoge@example.net

X-Spamd-Result: default: False [-5.21 / 10.00];
JUST_EICAR(0.00)[Eicar-Test-Signature];

送信ドメイン認証の結果がAuthentication-Resultsの項目に纏められて表示されるのでわかり易い。
今回ウイルスとしてEicar(ウイルスの代替として使われることが決まっている文字列)を利用したが、Rspamdはアタマが良いのか悪いのか、Eicarを認識するも無害として扱われてしまった。迷惑メールとしてもゼロ点扱い。Eicarはウイルス検知の挙動を確認するためのものだからこれは逆に良くないんじゃないかしら?


Rspamdのログを確認した。
2019-03-25 16:08:44 #9198(normal) ; lua; common.lua:90: clamav: virus found: "Eicar-Test-Signature - score: 1"

こちらはvirus foundになっているので(ClamAVがそう判断してるんだけど)、これでOK。

これまではPostfixに個別の機能のmilterを継ぎ足し継ぎ足しして何がなんだかわかりにくい状態になっていたが、全てRspamdに纏めたのでPostfix側はスッキリ。まぁ、SPFなど一部は既にRspamdに移ってたので言うほど個別機能が多かった訳ではないけど、設定して数年もすると設定した本人も忘れちゃうのよね。再び何がどうなっているのか理解するまでが大変。
継ぎ足し継ぎ足しは秘伝のタレだけで良い。

問題はRspamdが理解できる範囲を超えて肥大しすぎてて・・ワカラナイ

関連記事

エレコム トラックボール 大玉

トラックボールエクスプローラーを復活させて愛用していたんだけど、このところ親指で押す左クリックと右クリックに相当する2つのボタンの反応が恐ろしく悪くて、カチって押してるのに押されたことにならなくてイライラ。コピペすら満足にできない。なにぶんにも古いというのもあるが使いすぎもあってスイッチが悪くなっているのかと。
それで我慢できなくて、今回アマゾンでエレコムの人差し指で球を回すタイプのUSB接続版を購入した。トラックボールエクスプローラーの使い勝手とあまり変わらないかなと思ったので。更に、現行型番ではなく前の型番が安かったのでそちらにした。型番が変わっただけで物は同じと聞いてたし。

エレコム トラックボールマウス 有線 大玉 8ボタン チルト機能 ブラック M-HT1URXBKってやつね。

無線版もあるのにUSBの有線接続版を購入したのは、無線版マウスで電池が減ったときに操作性が悪くなってイライラした経験から。マウスと違い、トラックボールは本体を動かすわけではないのでコードが邪魔ということもないし。机上の見た目としてはコードがニョロっていうのは不要だけど。

エレコム トラックボールマウス 1
届いたパッケージ。大きいとも小さいともいえない。
右側のキチャナイのはトラックボールエクスプローラー。以前にラッピングしたけど使ってたら破れたので剥がして元に戻ってる

エレコム トラックボールマウス 2
パッケージの蓋を開いた。こんなパッケージでなくても良いんだけどなぁ。

エレコム トラックボールマウス 3
本体を取り出してトラックボールエクスプローラー(左)と並べてみた。エレコムの方(右)が一回り大きい。
大きすぎて持ち余すわけではなく、男性ならちょうど良い程度。
2つが並ぶとわかりやすいが、トラックボールエクスプローラーが左肩上がりで右手で楽に持てるようになっているのに対してエレコムのトラックボールは上面が水平に近い。そして手首側が分厚くなっている。

エレコム トラックボールマウス 4
球も52mmということでトラックボールエクスプローラー(左)より一回り大きい。トラックボールの球は大きくても持て余すことはないので大丈夫。より細かい操作に向くというだけ。(普通なら)
ただ、この製品の球は新品なのに全く転がりが良くない。

エレコム トラックボールマウス 5
球を外した状態のカップ部分はこんな感じ。
逆さにしても球が簡単に抜け落ちないように受け口の部分が少し窄まっているのだが、そのせいか回転には常に抵抗がかかっている感じ。指が受ける感触としては球受けの支持球周辺に酷く脂混じりの汚れが溜まってるときくらい。トラックボールエクスプローラーは昨年支持球を取り替えたばかりということもあり球をガッと転がすと1秒半ほど回転し続けるが、この製品はガッと転がして0.5秒もせずに停まってしまう。2.5mmの大型支持球というののありがたみはゼロ。支持球って回転すると勘違いしやすいけど、実際はまぁ回転なんかしない。支持球の1箇所が回転球と接触することになるわけだが、接触面積は狭ければ狭いほど回転球は抵抗無く動くので、支持球が削れたときを考えると大型支持球って意味あるのかしら?
そしてトラックボールならではのドット単位の制御はその抵抗のせいか細かく動かし難い。そっと1ドット分動かしたいのに、ギチッとした抵抗を感じるから力を入れるとカクッと急に動いて行き過ぎちゃう。←これは大げさな表現だが、大きな力は要らないとはいえそんな感じ。この製品では球を転がすには人差し指単独ではなく中指でサポートした方が良いのかもしれない。
もしかして、窄まっている部分をサンドペーパーかリューターで削ると回転良くなるのかしら?

エレコム トラックボールマウス 6
試しにトラックボールエクスプローラーの球をこの製品に嵌めて見るとブッカブカ。中で球がゴロンゴロン動くけどトラックボールとして使えなくもない。意外にもにも普通に反応する。

スイッチ類はまともな印象。少なくとも安っぽくはない。反応が悪いとかガタついて押す前と押した後で位置が微妙にずれるとかもない。ここはトラックボールエクスプローラーがあまりに安っぽくて雑な造りだったので何でもマシに見えるのかも。

エレコム トラックボールマウス 7
これはトラックボールエクスプローラーをいつも使うときのように持ったところ。自然に手に合うような形状なのでとても楽。

エレコム トラックボールマウス 8
エレコムのトラックボールを持ったところ。
写真では大して違いが無いように見えるけど、パームレストのせいなのかな?手首あたりから球の手前までの上面が柔らかいクッション素材で覆われていて、それ自体は悪くない筈なのだが、手首側+小指側が厚めになっているのは完全に蛇足。不自然に手首を持ち上げられるような感じになるのでパームレストの上に乗ってるのに手が疲れる感じ。
そして小指側がパームレストで持ち上げられる分反対の親指側が下がる感じになってしまうので手を内側にひねるようになる。 これも疲れるように感じるが、トラックボールエクスプローラーに慣れてしまったからかもしれない。慣れればこの疲れというのは感じないのかも。まだ使い始めたばかりなので違和感を受けてるだけかもしれない。

エレコム トラックボールマウス 9
今後も慣れないようならボールの下側から親指の下側辺りにメモ帳くらいの厚みのあるものを挟むと良いかもしれない。

トラックボールエクスプローラーは球受けの部分が赤く光るのだが、それが意外と眩しくてよろしくなかった。この製品は光学式ではあるが、可視光の波長内で光るのではないようなので眩しさは全くない。

ボタンだが、ホイールの下側が「左クリック」で、これは普通。しかしホイールの上側が2分割されていて、「戻る」「進む」になっている。「右クリック」は球の右隣の中指のボタン。これはマウスの操作で慣れている人からしても、トラックボールエクスプローラーで慣れてる人からしても違和感がある。Windows/Macであればボタンのアサイン変更アプリが用意されているようだが、Linuxではエレコムが提供するアプリが無いんだけど、xmodmapやLinux用に既にあるボタン割当て変更アプリでできるのかしら?
さらに薬指の位置にファンクションボタンがあり、球の左隣にもファンクションボタンが2つある。この球の隣の2つがとんでもなく押し難い場所。親指と人差し指の間に6本目の指がある人が開発したんじゃないのかと思う。人差し指をボールから離せということ?どう押せばよいのかわからない。
解像度の切り替えボタンが親指の下側の位置にあって3段階切り替えられるみたい。切り替えるとホイールのところが赤く点滅する。これが結構まぶしい。おそらく、初期値の「中」からあまり変えないんじゃないかしら。細かい動きも大胆な動きもどちらも簡単にこなせるのがトラックボールなんだから。

最新型番であるM-HT1URBKはエレコムのウェブサイトに書かれている「標準価格」では税込みで13,424円。もしこの価格でこの品質だったら許せないかな。自分なら一生恨むかも。
今回はアマゾンで古い型番のM-HT1URXBKを税込み3,790円で購入したわけだが、実質7割引で購入してもむしろ不満が多い。
この製品、型番は変わってもハードウエア自体は全く同じだよね?
パッケージに自慢気に書かれている「想像を超えた操球感」というのは残念ながら期待と真逆の「悪い」方に想像を超えていた。
もちろん、トラックボールが初めてなら「こんなものかな」かもしれないし、球の動きが悪いとはいってもさすがにマウスよりは楽に細かく動かせるので「(マウスよりは)操作しやすい」と思うかもしれない。でも、トラックボール愛好者からは評価されないんじゃないかな。

ここまで悪口に近いように書きながら、ガシガシ球を回しまくったら開封直後の酷く回転に抵抗する感じが少しだけ薄れてきた。さらに慣らしを頑張ればもっと回転しやすくなるのかしら?

2019年3月21日追記: (その後、自分で読んでも解りにくかったので追記を書き換えた+写真追加2枚)

「慣らしを頑張る」というのができなかったのでバフることにした。また、抵抗を感じる原因も調べることにした。

エレコム トラックボールマウス 13
この製品はボールを入れるカップ部分の縁が内側に5mm程度の帯で1周している。要するにリングになっている。 トラックボールの球は本来は支持球以外には外部と接触しない筈なので支持球の場所以外はボールとカップの間に隙間がなければおかしい。
そこで、細幅の付箋紙を深さ1cm程度カップと球の隙間に刺してグルリと1周まわしてみる。深く刺すと支持球でひっかかるがそれは問題ない。
今回購入したトラックボールは画像の水色の付箋紙が刺さっている部分で紙がひっかかった。つまりボールとカップがここで接触しているらしい。球がカップに触れてるとかありえないと思うが、製造後に検査とかしないのかしら?。
そこで、リング状になった奥側のエッジ1周と、特にこの接触部分?を重点的に磨いた(削った)。 このボールとカップが接触していると思われる場所はちょうど支持球の1つの近くで、その支持球の出っ張り具合が他の2つよりもだいぶ少ないので、想像するに支持球が正常な深さを超えて深く埋もれていている?それでボールの位置が微妙にズレて穴の奥側のリングのエッジ辺りに触れている?

エレコム トラックボールマウス 10
バフをかけると繊維くずは大量に出るけど粉は殆どでない筈。ボタンの隙間などにゴミが入るといやなので申し訳程度に養生テープを貼る。雑でも大丈夫。穴の奥のセンサー部分にも貼っておくとなお良い。

エレコム トラックボールマウス 11
使うのはリューターとフェルトバフ。これは本当は金属磨き用で樹脂はNG。リューターは速度調整できるやつを最低速度で使う。500円以内で買えるようなペン型の速度固定のやつは速すぎるので樹脂が溶けちゃう。

エレコム トラックボールマウス 12
こんな感じで5mm幅のリング状になっている穴の奥側のエッジのバリがあるように感じる部分を中心にエッジを撫でるように磨く感じ。力は入れず軽く当てる程度で。同じところを擦り続けると摩擦熱で溶けることになる筈なので注意。リング状になった部分より奥、特に支持球辺りから奥の丸く凹んだカップの部分は触らない。

エレコム トラックボールマウス 14
上の写真は削って、毛埃を吹き飛ばして養生テープを剥がしただけの状態。テープの跡とかスリ傷っぽいのも見えているが、未掃除。拭き掃除しちゃうと削った部分があまり判らないかもしれないので。
赤い矢印のあたり、リングの縁を削ってその下のカップとの段差が殆どなくなった。

紙を通すとひっかかっていた部分もスッと通るようになった。
実際に使用すると、動かし始めのギチっとしたひっかかりは無くなり、ヌメっとした抵抗感も2/3程度解消した。それでもまだトラックボールエクスプローラーの支持球部分に汚れが溜まったくらい or 支持球交換前の支持球が削れた状態程度の動き。
ガッと球を回してもやっぱりすぐ停まる。どうしたものか。

今のところ気に入ってはいないけど、球を回そうとしたときの動きはじめのひっかかりがなくなったのですぐに買い換えなければというほどではなくなった。暫く使ってみようと思う。

WordPress他でページ本文部分をAMP化するための一歩

HTMLで書かれたウェブサイトをAMP化して表示を行うにあたり、HTMLタグをAMP-HTMLタグに書き換えたりAMPでは書いてはいけない属性を削除するなどの処理が必要になる。 WordPressでは既にAMP表示用のプラグインが提供されていたりAMP化処理を実装したテーマなどが出ているが、ここぞというところだけ変換したいということはある。WordPressオフィシャル(Automattic)のAMPプラグインのClassicモードでは特にそう。
もしかしたらAMPプラグインの変換用の関数が簡単に利用できるかもしれないが、AMPプラグインのソースを見るのが面倒だったのと変換の必要な細かい部分全てを網羅しようと思わなければ自分で関数を作ってしまっても良いかもしれない。と「がとらぼ」の中の人は思って自作のWordPress用テーマBonyo/凡庸用に書いてみた。その内容。

ページのbody部分をAMP用に書き換える際のテキトールール

  • HTMLタグにstyle属性をつけたらダメ
  • HTMLタグにonclickイベントハンドラ属性をつけたらダメ
  • aタグにtarget属性は敢えて書かない(_blankは可だけど要らないでしょ?)
  • scriptタグは禁止 (JSON-LD可だけど元本文にはそんなの無いでしょ?)
  • styleタグは禁止 (body内には書けない)
  • imgタグではなくamp-imgを使う
  • audioタグではなくamp-audioを使う
  • videoタグではなくamp-videoを使う
  • iframeタグではなくamp-iframeを使う
  • base, frame, frameset, object, param, applet, embedタグは使用禁止

イベントハンドラ属性はたくさんあるが、この記事では特に使うことの多いonclickだけ。必要なら他のも足していただければと。
amp-adとかyoutube-adなど幾つかの専用タグは今回は対象外とする。

処理用の関数

処理としては文字列の置換を使うのもありだろうが、前回の画像遅延表示で本文を書き換えたのと同じくDOMで行う。むしろ画像遅延表示の置換よりも今回のAMPの方がDOM向きかと個人的には思う。

1
2
3
4
5
6
function 関数名($content) {
    //1. $contentをDOMDocumentに読み込む処理
    //2. 変換・削除などの処理
    //3. DOMDocumentを出力する
    return 出力内容;
}

$contentをDOMDocumentにする

1
2
3
4
$buf  = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body>';
$buf .= $content;
$dom = new DOMDocument();
@$dom->loadHTML($buf, LIBXML_HTML_NODEFDTD | LIBXML_NOERROR);

文字化けさせない為に<html>タグで包みHTML文書として完成させる。そのときにHTMLヘッダで文字エンコーディングとしてUTF-8を指定する。新しいDOMDocumentを作ってloadHTML()で$contentを読み込む。このときloadHTML()にオプションを指定してLIBXML_HTML_NODEFDTDで<!DOCTYPE hoge>を付けさせないのと$contentがXMLとして正しくない場合にエラーにさせない?ようにする。

不要な属性を削除する

1
2
3
4
5
foreach ($dom->getElementsByTagName('*') as $node) {
    $node->removeAttribute('style');
    $node->removeAttribute('target');
    $node->removeAttribute('onclick');
}

DOMDocment内の全てのタグをgetElementsByTagName()でノードとして得て、順に評価し、不要な属性をremoveAttribute()で取り除く。

タグの付け替え

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$arrtags = array('img', 'iframe', 'video', 'audio');
foreach ($arrtags as $convtag) {
    foreach ($dom->getElementsByTagName($convtag) as $node) {
        $newtag = 'amp-' . $convtag;
        $newEle = $dom->createElement($newtag , '');
        $node->parentNode->appendChild($newEle);
        foreach ($node->attributes as $nodeattr) {
            $newEle->setAttribute($nodeattr->nodeName, $nodeattr->nodeValue);
        }
        $node->parentNode->removeChild($node);
    }
    unset($convtags);
}

タグの置換というのは例えばimgをamp-imgというのに付け替えるやつだけど、DOMDocumentでは単に名前を変えるというようなのはできないようなので、新しい要素としてamp-imgを作成して、元のimgノードの親に対して新しく作ったamp-imgをappendChild()する。imgノードの親の子供なので元のimgノードと同列の兄弟になる。元のimgノードに付いていた属性と同じ属性を順にamp-imgノードに付ける。
最後に親の子供である元のimgノードを削除する。これでimgノードがamp-imgに変わったのと同じになる。

Formにtarget属性を付ける

1
2
3
foreach ($dom->getElementsByTagName('form') as $node) {
    $node->setAttribute('target', '_top');
}

これは自分の中の元々のAMP化の知識には無かった部分。AMPでFormが使えるようになってからFormにはTargetを付けないとAMPエラーになるみたい。結果の出力先であるtargetは_blankである必要はなく何でも良いっぽい。
なのでFormタグのノードにtarget属性をセットして属性値として_topを指定した。
それだけ。

出力

1
2
3
$buf = preg_replace('/^\<html\>.*?\<body\>/', '', $dom->saveHTML());
$buf = preg_replace('/\<\/body\>.*?\<\/html\>\n/', '', $buf);
return $buf;

DOMDocumentをsaveHTML()で出力して前後の不要なタグを削除する。
最後に関数の返り値にする。

まとめた

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function 関数名($content) {

    $buf  = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body>';
    $buf .= $content;
    $dom = new DOMDocument();
    @$dom->loadHTML($buf, LIBXML_HTML_NODEFDTD | LIBXML_NOERROR);

    //delete attributes
    foreach ($dom->getElementsByTagName('*') as $node) {
        $node->removeAttribute('style');
        $node->removeAttribute('target');
        $node->removeAttribute('onclick');
    }

    //converting tags    <img>, <iframe>, <video>, <audio>
    $arrtags = array('img', 'iframe', 'video', 'audio');
    foreach ($arrtags as $convtag) {
        foreach ($dom->getElementsByTagName($convtag) as $node) {
            $newtag = 'amp-' . $convtag;
            $newEle = $dom->createElement($newtag , '');
            $node->parentNode->appendChild($newEle);
            foreach ($node->attributes as $nodeattr) {
                $newEle->setAttribute($nodeattr->nodeName, $nodeattr->nodeValue);
            }
            $node->parentNode->removeChild($node);
        }
    }

    //removing some tags.
    $convtags = array('script', 'style', 'base', 'frame', 'frameset', 'object', 'param', 'applet', 'embed');
    foreach ($convtags as $convtag) {
        foreach ($dom->getElementsByTagName($convtag) as $node) {
            $node->parentNode->removeChild($node);
        }
    }

    //adding target to form
    foreach ($dom->getElementsByTagName('form') as $node) {
        $node->setAttribute('target', '_top');
    }

    //output
    $buf = preg_replace('/^\<html\>.*?\<body\>/', '', $dom->saveHTML());
    $buf = preg_replace('/\<\/body\>.*?\<\/html\>\n/', '', $buf);
    return $buf;
}

こんな感じ。