Flash Player 9,0,115,0のに対処する方法

いまさら画期的な発見!まずは長い前置きから。


最新のFlash Playerでは、SocketまたはXMLSocketを使ってサーバーと通信するとき、最初に<policy-file-request/>\0(\0はNULL文字)という文字列を送信してきます。これに対して<cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>\0というような文字列を返してやらないと、まったく通信できません。


これは(HTTPサーバーでホスティングする)crossdomain.xmlファイルとは関係ありません。SocketとXMLSocketではcrossdomain.xmlファイルは無視されます。


厄介なことに、XMLSocketだけでなくSocketを使ったときでもこのセキュリティポリシーが適用されるため、Partty!.orgのようにXML以外のプロトコルでサーバーと通信したい場合(Partty!.orgはTelnetとIRC)、サーバープログラムにおかしな変更を加える必要があります。
特にTelnetはサーバー側から通信が開始されるプロトコルなので、クライアントがFlashの場合(Flashが勝手に<policy-file-request/>\0を送ってくる)と、telnetコマンドがクライアントの場合(サーバーから何か送られてくるまで何もせずに待っている)の切り分けが難しく、それぞれを別のポートで待ち受ける実装になっています。


一応、n秒以内に<policy-file-request/>\0が送られてきたら<cross-domain-policy>...\0を送り、来なかったら本来の通信を開始する…とタイムアウトを使って切り分けることもできますが、そうするとtelnet、Flashのどちらでもタイムアウトを待つ無駄な時間が発生してしまいます。
telnetコマンドで繋げたときにタイムアウト待ちが発生することは分かると思いますが、Flashで繋げたときにもタイムアウト待ちが発生するのは、Flashは<policy-file-request/>\0を送信して待ち受けるコネクションとは別のコネクションをもう一度張ってから本来の通信を開始するためです。



と、ここまでが前置きで、ここからが解決編です。


Socketのセキュリティポリシーに関する仕様は、Policy file changes in Flash Player 9に書いてあります。

7ページもあって読む気にならないのですが、Socket master policy filesConfiguring socket policy filesよると、Socketクラスで通信するポートに<policy-file-request/>\0を送る前に、まずTCPの843番ポートに<policy-file-request/>\0を送る仕様になっているようです。
843/tcpで3秒以内に応答が得られなかった場合のみ、本来のポートに<policy-file-request/>\0を送るようです。


つまり、843/tcpで<cross-domain-policy>...\0を返すサーバープログラムを動かしておけば、TelnetやIRCサーバーの方に変更を加える必要が無くなるわけです。

new flash security policies - Untitledで843/tcpで待ち受けるサーバープログラムをPHPで書いた例が紹介されていますが、Rubyでも簡単に書けます。

require 'socket'
require 'rexml/document'

if ARGV.empty?
  puts "Usage: #{File.basename($0)}  <path/to/crossdomain.xml>"
  exit 1
end

policy = File.read(ARGV[0]) + "\0"

REQUEST_LIMIT = 32
def log(addr, msg)
  puts "#{addr}  #{msg}"
end

srv = TCPServer.open(843)
while true
  Thread.start(srv.accept) {|s|
    port, addr = Socket.unpack_sockaddr_in( s.getpeername )
    begin
      log addr, "connection accepted"

      buf = ""
      begin
        buf << s.sysread(REQUEST_LIMIT)
        raise "request too big" if buf.length > REQUEST_LIMIT
      end until buf.include?("\0")

      log addr, "received request #{buf.inspect}"

      req = REXML::Document.new(buf)
      unless req.root.name == "policy-file-request"
        raise "invalid request"
      end

      log addr, "sending policy file"
      s.write policy

    rescue
      log addr, "invalid request: #{$!.inspect}"
    ensure
      s.close
    end
  }
end

ちょっと堅めに書いてます。


これでサーバー側のデーモンを一切変更する必要なしに、TelnetでもIRCでもXMPPでもSSHでもIMAPでもFTPでもCIFSでも何でも、ActionScriptでクライアントが書けちゃいますねー。
listenできないのとUDPが使えないのは玉に瑕。



※追記
LiveDocsのSecurity.loadPolicyFile()のところに詳しい説明が書かれている!前はこんな説明は書いてなかったハズ!
(しかしHTTPの場合とXMLSocketの場合が混ざっていてイマイチ分からない)