daemonfreaks.com

コメントスパム対策系プラグインでトラックバックまでrejectされる問題

posted by jun-g at Mon, 18 Dec 2006 02:37 JST

こないだ書いたcheck_javascriptプラグインを使えばtrackbackプラグインへの修正は不要、っていうのは嘘でした。すみません。check_javascriptプラグインを導入すると、トラックバック受信時にエラーが発生するようになります。

どうもこの辺り、設計がいけてない気がする。ちゃんと仕組みを整理した上で修正したいので、ちょっとまとめてみる。

まず、そもそも「trackbackプラグインがcommentsプラグインの機能を使用する事を前提に実装されている」という事が問題だと考えられる。しかし、これを言い出すと、trackbackプラグインをスクラッチから書き直す事になってしまう。本来はその方が良いのかもしれないけど、ちょっと大変そうなので今回はパス。

次に、shunuhsさんがsh1.2 pyblosxom : 続: MagicWord/comments/trackback pluginで取った「トラックバック経由で受け取ったデータにtrackbackプラグイン内部で判定用パラメータを追加し、コメントスパム対策系プラグイン内でトラックバックか判定する」という対策の場合、使用するコメントスパム対策プラグイン全てに「受け取ったデータはトラックバックか?」という判定処理を加える必要があるし、既にPyblosxom Plugin Registryに登録されているコメントスパム対策系プラグインは、やはりそのままでは使えない事になる。できればこれらのプラグインを修正無しで使用できるようにしたい。

以上の事を(ほんのちょっとだけ)考えた結果、「cb_comment_reject関数はwriteComment()を呼び出す前、しかもtrackbackプラグインでは実行されない関数内で実行するようにすればいいのでは?」という結論に至った。

ということでcomments.pyを早速修正。svn diffの結果は以下の通り。

Index: comments.py
===================================================================
--- comments.py	(リビジョン 954)
+++ comments.py	(作業コピー)
@@ -359,19 +359,6 @@
         os.makedirs(cdir)
 
     cfn = os.path.join(cdir,entry['fn']+"-"+comment['pubDate']+"."+config['comment_draft_ext'])
-     
-    argdict = { "request": request, "comment": comment }
-    reject = tools.run_callback("comment_reject",
-                                argdict,
-                                donefunc=lambda x:x != 0)
-
-    if (isinstance(reject, tuple) or isinstance(reject, list)) and len(reject) == 2:
-        reject_code, reject_message = reject
-    else:
-        reject_code, reject_message = reject, "Comment rejected."
-
-    if reject_code == 1:
-        return reject_message
    
     def makeXMLField(name, field):
         return "<"+name+">" + cgi.escape(field.get(name, "")) + "</"+name+">\n";
@@ -475,13 +462,9 @@
         comment_dir = os.path.join(config['comment_dir'], entry['absolute_path'])
 
         # create the message
+        from email.Header import Header
         from email.MIMEText import MIMEText
         message = []
-        message.append("From: %s" % email)
-        message.append("To: %s" % config["comment_smtp_to"])
-        message.append("Date: %s" % formatdate(float(comment['pubDate'])))
-        message.append("Subject: comment by %s" % author)
-        message.append("")
         message.append("Name: %s" % author)
         if comment.has_key('email'):
             message.append("Email: %s" % comment['email'])
@@ -498,7 +481,13 @@
 
         body = '\n'.join(message)
         body = MIMEText(body.encode('utf-8'), 'plain', 'utf-8')
- 
+        subj = "comment by %s" % author
+        subj = Header(subj.encode("utf-8"), "utf-8")
+        body["Subject"] = subj
+        body["From"] = config["comment_smtp_from"]
+        body["To"] = config["comment_smtp_to"]
+        body["Date"] = formatdate(float(comment['pubDate']))
+
         if (config.has_key('comment_mta_cmd')):
             argv = [config['comment_mta_cmd'],
                     '-s',
@@ -506,7 +495,7 @@
                     config['comment_smtp_to']]
             # TODO: switch to subprocess when we can require python 2.4
             process = popen2.Popen3(argv, capturestderr=True)
-            process.tochild.write(body)
+            process.tochild.write(body.as_string())
             process.tochild.close()
             process.wait()
             stdout = process.fromchild.read()
@@ -521,7 +510,7 @@
             server = smtplib.SMTP(config['comment_smtp_server'])
             server.sendmail(from_addr=email,
                             to_addrs=config['comment_smtp_to'], 
-                            msg=body)
+                            msg=body.as_string())
             server.quit()
 
     except Exception, e:
@@ -714,9 +703,22 @@
             cdict['email'] = form['email'].value
 
         cdict['ipaddress'] = pyhttp.get('REMOTE_ADDR', '')
-        data["comment_message"] = writeComment(request, config, data, \
-                                                cdict, encoding)
 
+        argdict = { "request": request, "comment": cdict }
+        reject = tools.run_callback("comment_reject",
+                                    argdict,
+                                    donefunc=lambda x:x != 0)
+        if (isinstance(reject, tuple) or isinstance(reject, list)) and len(reject) == 2:
+            reject_code, reject_message = reject
+        else:
+            reject_code, reject_message = reject, "Comment rejected."
+        if reject_code == 1:
+            data["comment_message"] = reject_message
+        else:
+            data["comment_message"] = writeComment(request, config, data, \
+                                                   cdict, encoding)
+
+
 def massage_link(linkstring):
     """Don't allow html in the link string. Prepend http:// if there isn't
     already a protocol."""

こないだの修正分も含まれているのでちょっと紛らわしいかも。この差分の内容には以下の修正が含まれている。

  • cb_comment_rejectをwriteComments()実行前に処理するように変更
  • コメント/トラックバック内容に非ASCII文字が含まれていた場合にメール送信時にエラーになる問題を修正
  • 「From」「To」「Subject」「Date」がメールヘッダではなくメール本文に設定されてしまう不具合を修正

これで、スパム対策プラグインを導入しても、修正無しにトラックバックの受信が行えるようになるはず。

しかし、上記の修正を適用するとトラックバック受信時にcb_comment_reject関数が実行されなくなってしまう為、トラックバックスパム対策を行うフックが無くなってしまう。これは、trackbackプラグイン側にフックを用意すれば良いと思う。trackback.pyを以下のように修正してみた。

Index: trackback.py
===================================================================
--- trackback.py	(リビジョン 954)
+++ trackback.py	(作業コピー)
@@ -96,6 +96,19 @@
                       'link' : form['url'].value, \
                       'source' : form.getvalue('blog_name', ''), \
                       'description' : form.getvalue('excerpt', '') }
+
+            argdict = { "request": request, "comment": cdict }
+            reject = tools.run_callback("trackback_reject",
+                                        argdict,
+                                        donefunc=lambda x:x != 0)
+            if (isinstance(reject, tuple) or isinstance(reject, list)) and len(reject) == 2:
+                reject_code, reject_message = reject
+            else:
+                reject_code, reject_message = reject, "Trackback rejected."
+            if reject_code == 1:
+                print >> response, tb_bad_response % reject_message
+                return 1
+
             from Pyblosxom.entries.fileentry import FileEntry
             from Pyblosxom.pyblosxom import Request
             from Pyblosxom.pyblosxom import PyBlosxom

この修正を適用すると、トラックバック処理時にcb_trackback_reject関数が実行されるようになる。トラックバックスパム対策には、この関数を持つプラグインを実装すれば良い。

コメントとトラックバックのスパム対策に同じ処理(IPアドレスチェックとかNGワードチェックとか)を使用したい場合は、

def cb_comment_reject():
    spam_check()

def cb_trackback_reject():
    spam_check()

def spam_check():
    (処理)

みたいなプラグインの実装にすれば良いと思う。どうせこれまでに存在したコメントスパム対策プラグインはトラックバックに対しては機能しなかったはずなので、この仕組みで問題ないはず。

と、まぁ自分なりの修正案をガーっと書いてみた訳やけど、実はあんまり動作確認もしてないし、Pyblosxom自体の仕組みもソースを斜め読みした程度しか理解してないので、もしかしたらデタラメな事書いてるかも。ツッコミ等ありましたらよろしくお願いします。> pyblosxomユーザーの皆様

とりあえず自分のところには、上記修正を適用したcommentsプラグインを導入してみて、問題無さそうならパッチをMLに投げてみる事にしよう。trackbackプラグインの修正は…もうちょっと考えてみよう。

posted by shunuhs at Thu Dec 21 01:18:13 2006

先日MLに送ったtrackback/magicwordに対して返答があった
のですが、trackback側で実施したcdictへの値の追加が
Ryanにはどうも気に入らなかったらしいので、じゃあ
jun-gがこういうの(↑)考えてるけど、どう?って勝手に
振ってしまいました^^; すいません。

もしよかったらこんなのどう?ってpatch投げてやってください。

posted by jun-g at Thu Dec 21 04:24:47 2006

ありがとうございます!早速パッチ投げときました。かなり説明不足なメールを投げてしまったような気もしますが…。

上記パッチのうちメール送信部分に関しては、「comment_mta_cmd」のパスが未テストなので今回は含めずに投げました。
こっちも早く取り込んでもらわないと…。

Comments / TrackBacks

TrackBack URL for this entry: https://www.daemonfreaks.com/blog/trackback/200612180211








コメントスパム対策の為、各入力項目へのURL記載は全てリジェクトしてます。ごめんなさい。