shinjuku.ex#7

久しぶりに開催されたshinjuku.exに参加。今年に入ってからelixir界隈はにぎわってきていて、
Programming Elixir
のような書籍が現れてきた。他にもオライリーからも出版されてきている。

そんな背景と関係あるのか無いのか、今回はKDDIウェブコミュニケーションズが会場であった。

SigilかわいいよSigil

Sigilとはリテラルの拡張にあたるもので、言語組み込みとしては正規表現の%rやバイナリの%bが存在している。elixirではユーザ定義のSigilを使うことができるので今回はそのネタ。

Sigilの定義の仕方

言語で予約されているSigilは以下のとおり

b バイナリ文字列(エスケープあり, ””と同様)
B バイナリ文字列(エスケープなし)
c 文字リスト(エスケープあり, ''と同様)
C 文字リスト(エスケープなし)
r 正規表現(エスケープあり)
R 正規表現(エスケープなし)
w ワードリスト(エスケープあり)
W ワードリスト(エスケープなし)

コレ以外で文字を選択するのが吉であり、ここではlとする。大文字を小文字にするSigilである。
モジュール中にsigil_lという関数を定義する。こんな感じ。

defmodule Sigill do
  def sigil_l(a, _opt) do
    String.downcase(a)
  end
end

このSigilを使う側は、モジュールSigillをimportする必要がある。

iex(1)> import Sigill; IO.puts(%l(ABC))
abc
:ok

Elixir中に他の言語を埋め込むための構文として使えそうだ。

ex_doc

elixirは「ドキュメントが第一級のオブジェクト」という面白い概念をもっている。どういう事かというと、プログラムのドキュメントをプログラムから操作できるという事である。doxygenなどは、プログラムのコメントを書式化するものだが、elixirではモジュール属性として設定されているモジュールドキュメントを書式化する。そのためのツールがex_docである。

モジュールドキュメント

@moduledocあるいは、@docによりドキュメントを記述する。
書式はmarkdownがそのまま使えるがインデントのベースが@moduledoc/@docが置かれたレベルであることに注意する。

defmodule M1 do
  @moduledoc """ 
  これはM1モジュールです。
  """
  @doc """
  func1はnil値を返します。
  """
  def func1() do
     nil
  end
end

コンパイルの仕方

mixを使っている場合、
mix.exsに

  defp deps do
    [ {:ex_doc, github: "elixir-lang/ex_doc" }
    ]
  end

としておき、

  mix docs

でdocsディレクトリにドキュメントが生成される。

erlang like record manipulator

rabbitmqのクライアントamqp_clientをelixirで使おうと思ったのだが、erlangのrecordを使い倒していて困った。elixirにもレコードはあるし、erlangレコードをコンバートする事も出来るのだが、-includeを追いかけてくれないとかのelixirのRecord.extract_fromがイマイチだったり、レコードタグ(タプルの最初の要素のアトム)の値をたよりにOK/NGを判定するamqp_clientの仕様が、レコードタグをモジュール名にしてしまうelixirレコードの仕様と合わなかったりしたので、Ermモジュールという名前でマクロライブラリを自作することにした。
Amqpモジュールを例にすると、こんな感じで使う。

defmodule Amqp do
  use Erm
  use Amqp.Uri
  Erm.addpath("dist/rabbit_common*/ebin")
  :io.format("code ~p~n", [:code.lib_dir(:rabbit_common)])
  Erm.defrecords_from_hrl("deps/**/amqp_client*/include/amqp_client.hrl")

  def start() do
    {:ok, re} = Amqp.Uri.parse("amqp://user:passwd@amqp-server-host.example.com")
    :io.format("Refields: ~p~n",
                 [Erm.record_info(:fields, :amqp_params_network)])
    :io.format("Re: ~p~n", [re])
    {:ok, con} = Amqp.Connection.start(re)
    {:ok, chan} = Amqp.Connection.open_channel(con)
    ex = Erm.record(:"exchange.declare", [exchange: "my_exchange",
                   type: "topic"])
    Erm.recordl(:"exchange.declare_ok") = Amqp.Channel.call(chan, ex)
    q = Erm.record(:"queue.declare", [queue: "my_queue"])

    Erm.recordl(:"queue.declare_ok") = Amqp.Channel.call(chan, q)
    binding = Erm.record(:"queue.bind", [queue: "my_queue",
                                         exchange: "my_exchange",
                                         routing_key: "key"])
    Erm.recordl(:"queue.bind_ok") = Amqp.Channel.call(chan, binding)

    payload = "foobar"
    publish = Erm.record(:"basic.publish", [exchange: "my_exchange",
                                            routing_key: "key"])
    p = Erm.record(:"P_basic", [delivery_mode: 2])
    p = Erm.record(:"P_basic", p, [delivery_mode: 2])
    Amqp.Channel.cast(chan, publish, Erm.record(:amqp_msg, [props: p,
                                                            payload: payload]))

    :timer.sleep(10000)
    get = Erm.record(:"basic.get", [queue: "my_queue", no_ack: true])
    {Erm.recordl(:"basic.get_ok"), content} = Amqp.Channel.call(chan, get)
    Erm.recordl(:amqp_msg, [payload: payload2]) = content
    :io.format("~p ~p", [payload, payload2])
    binding = Erm.record(:"queue.unbind", [queue: "my_queue",
                                          exchange: "my_exchange",
                                         routing_key: "key"])

    Erm.recordl(:"queue.unbind_ok") = Amqp.Channel.call(chan, binding)
    delete = Erm.record(:"exchange.delete", [exchange: "my_exchange"])
    Erm.recordl(:"exchange.delete_ok") = Amqp.Channel.call(chan, delete)
    delete = Erm.record(:"queue.delete", [queue: "my_queue"])
    Erm.recordl(:"queue.delete_ok") = Amqp.Channel.call(chan, delete)
    :ok = Amqp.Channel.close(chan)
    :ok = Amqp.Connection.close(con)
  end
end

eppかわいいよepp

erlangのコードを解析してelixirのコードにしてそれをコンパイルするとerlangのコードになるのだが、それは置いておいて、erlangのコードをプリプロセスするためのモジュールeppがあるのでそれを使う。

{:ok, ast} = :epp.parse_file(file, pathlist, opt)

opt, pathlistは何に使うのかイマイチ不明だったのでどちらも[]でOK。fileはbinaryではなくlistであることに注意すると、erlangの抽象構文木が得られる。この構文木をスキャンしてレコード定義部分を抽出してコンバート(Enum.filter_map/3)すれば良い。

レコード定義

レコードはタプルである。従ってタプル定義を登録しておき、あとで名前で参照したときに定義からタプルを生成することができれば良い。
定義情報をマクロのモジュールやメソッドとして保存する方法もあるし、defrecordpとかそのために用意されている節もあるのだが、amqp_clientのレコード名は、P_basicとかqueue.bind_okとか香ばしくてメソッド名に使えないため、etsを使う。
レコード定義は、フィールド名とその初期値のリストのetsテーブルへのinsertであり、レコード定義の参照は、etsテーブルのlookupとなる。

include_libの解決

:epp.parse_fileでは-include_libのファイルオープンエラーは {:error, {_n, :epp, {filepath}}}の形でastに表現される。これを見つけたら例外を発生させればよい。

raise File.Error, reason: :enoent, action: "maybe not search path", path: filepath

といった具合。サーチパスは、:code.add_path('path’)で追加。Path.wildcard("path/**/to/include")のようなかたちでまとめて追加しておくと良い。これはコンパイル時に追加しなければならないので関数定義の外側に置いておく。

astはconvすればいい

さてastから{:attribute, _n, :record, {name, fields}}の形を取り出してetsへ登録すればいいのだが、ここでfieldsの内容がきわめて多様であるためそんな簡単には行かない。まぁ木自体は出来ているので、そこを降りていくだけなのでそんなに難しくはないのだが。
fieldsは初期値のない{:record_field, _n, name}か初期値のある{:record_field, _n, name, value}の形をしている。初期値がない場合には:undefinedを初期値と考えればいいので、そのようにすればいいが、valueについては、任意のerlang項があり得るため、コンバートが必要となる。このコンバート関数をconvとしてどういうパターンがあるか考えてみる。

conv!conv!conv!

{:atom, _n, v} アトム v
{:integer, _n, v} 整数 v
{:bin, _n, v} バイナリ v
{:record, _n, name, fields} レコード参照 erec(name, reduce_field(fields))
{:function, func, arity} 関数 [:erlang, func, arity]
{:function, module, func, arity} 関数 [module, func, arity]
{:fun, _n, fp} 無名関数 {:function, [import: Kernel], conv(fp)}
{:cons, _n, car, cdr} コンス(つまりリスト) [ conv(car) | conv(cdr)]
{:call, _n, mf, args} 定義時に関数呼び出す appy(m, f, args)

といったパターンがある。一文字でない変数で表示した変数は非終端記号であり、さらにconvする必要があるが、上の表の通り、再帰的に書くだけなので問題は無い。面白いのは関数呼び出しや関数オブジェクトも変換対象と出来るのでほぼ全てのerlangの構文をコンバートできてしまっている。

右辺値と左辺値

パターンマッチングでも使いたいが、レコード定義には初期値がもれなく入っているため、レコード参照を仮にErm.record(:record_name)とした時、-record(record_name, {field1})の場合、
Erm.record(:record_name) --> {:record_name, :undefined}
と展開されてしまうため、左辺値としてマッチングに使おうとすると、興味の無いフィールドを明示的に'_'として与えなければならなくなる。これでは使いにくいため、左辺値として使うときには、フィールドのデフォルト値を'_'として展開するようにする。

というところをまとめたのが、
https://github.com/k1complete/erm
になる。。。

韓国への「サイバー攻撃」続報その4

Google 翻訳
によると、

21日、放送通信委員会は "去る20日、金融·マスコミのコンピュータ·ネットワーク麻痺を起こしたサイバーテロが一般国民に拡散する可能性と追加攻撃が発生する可能性など、あらゆる可能性を開いておいている"とし、 "個人でもPCを起動しながら、コンピュータの中の時間を変更してワクチンプログラムをダウンロードするなどの予防措置をとるのがいい "と強調した。

とのこと。ほぼ割れ決定ですね。

格安でWindowsを販売する楽天の”韓国館”。正規品だというけど、マイクロソフトに聞いてみた。: Think ism

韓国での「サイバー攻撃」の続報その3

ついに原因判明。デスブログに以下のエントリーがあることが判明した。

久しぶりに食べたコリアンは
とってもおいしかったです〜♪



キムチのお土産までいただいて
お腹もいっぱーーい!

攻撃元が中国のIPだったが、実は勝手に韓国の農協内部で使っていたとか、そんなことは結果に過ぎないだろう。

なお、例のIP 101.106.25.105 を逆引きすると 1695750.r.msn.com になってMSのサーバであるというのは、ガセだと思う(実際にやってみればわかること)。

ただし、繰り返すが対処策が時刻を戻してアップデートをしないというのは、やはり怪しい。正規のサーバに対してSP1へアップデートするだけでいい筈。

韓国での「サイバー攻撃」の続報

http://itpro.nikkeibp.co.jp/article/NEWS/20130321/464942/によると、WSUSがクラッキングされたのが原因と発表している。

 マルウエア対策を実施しているはずの大手企業が被害を受けたのは、企業内でセキュリティパッチを一括管理する更新管理サーバーがハッキングされたのが原因と発表した。これにより、マルウエアの企業内への侵入を許してしまったとしている。
 農協銀行のシステムを分析したところ、中国のIPアドレス(101.106.25.105)がパッチ更新管理サーバーに接続して、悪意あるファイルを作成していたことが確認されたという。

この悪意あるファイルを各クライアントがダウンロードしないと被害が成立しないのだが、そこは立証されているのかは不明。

 被害拡大を防ぐため、政府、公共機関、交通・電力などのインフラ企業、病院などは、コンピュータのBIOSの時間設定を一時的に2013年3月20日午後2時以前に変更している。今回のサイバー攻撃ではWindowsの自動更新のタイミングで一斉に被害が発生したため、当面は自動更新を回避するという手当てだ。

Windowsの自動更新で発動するのは確定のようである。というか、WSUS落として運用すればいいと思うのだけど駄目なのかなぁ。

韓国での「サイバー攻撃」の簡単なまとめ

http://www3.nhk.or.jp/news/html/20130320/k10013333751000.html
によると、

韓国で20日午後、主要な3つのテレビ局や大手銀行などで社内のイントラネットがほぼ同じ時間帯から使えなくなり、韓国政府は、「ハッキングにより不正プログラムが流されたことを確認した」と発表し、何者かによるサイバー攻撃の可能性が高いとみて詳しい状況を調べています。

とのことだ。このニュース動画によると"Operating System Not found"という表示が読み取れ、MBRが破壊された物と思われる。

ところで、
http://uni.2ch.net/test/read.cgi/newsplus/1363783710/

によると、Windows7にはアクティベーションの回避技にMBRを使った技があり、この技を使って正規のアクティベーションを回避した状態でSP1を当てるとシステムが起動しなくなるということがわかっている。そして、2013/3/19からSP1の強制アップデートが開始された(順次強制適用されるということ)。

128 +7:名無しさん@13周年 :sage:2013/03/20(水) 22:11:40.79 ID: NnrTug/uP (1)
win7 SP1 強制うpデート開始
http://gigazine.net/news/20130319-win7-sp1-rolling-out/

   ↓↓↓↓

160 三毛(catv?) sage New! 2013/03/20(水) 18:57:13.70 id:bDliyWCV0
>>114
調べてたら思い出した。
Windows 7アクティベーション回避法のうちの1つにMBRを使った方法があって、
これを適用した環境でService Pack 1をインストールすると起動できなくなる、
というトラブルがあったはず。
もしかしてこれに引っかかったんじゃ・・・・・

   ↓↓↓↓

放送・金融電算麻痺、悪意のあるコードは、PCブート領域の破壊"
20日午後、KBS、MBC、YTN [040300]、新韓銀行、農協など放送・金融機関の電算網を麻痺させた、
悪意のあるコードは、これらの機関のPCブート領域(MBR)を破壊したことが明らかになった。
政府は、放送通信委員会、警察庁韓国インターネット振興院などにサイバー脅威合同対策チームをKBSなど派遣、
被害を受けたPCからマルウェアのサンプルを入手して分析した結果、このよう明らかになった。
放送通信委員会は同日午後、2次ブリーフィングで"被害機関から採証した悪意のあるコードを分析した結果、
特定のベンダーのアップデータ管理サーバーPMS)で悪性コードが流布されたものと推定される"と説明した。
しかし、悪意のあるコードが流布された具体的なポイント等については確認していなかった。
合同対策チームは、国の公共機関の被害がないものと認識しており追加のマルウェアなどの攻撃に備え、
全機関に警戒を強化して攻撃発生時の迅速なリカバリシステムを稼動するように措置した。
政府はコンピュータ・ネットワーク麻痺の原因が分析されるように国家サイバー安全戦略会議を開き、国家レベルのフォローアップなどを議論する予定である。
(ソウル=連合ニュース)


これ、ぶっちゃけほぼ原因確定ですね。

ついでながら、過去にマイクロソフトが韓国軍にソフト使用料の支払いを求めていたという。
聯合ニュース
つまり、韓国軍は当時正規にアクティベートしていないで使用していた可能性は高い。そして、この記事にもある通り、報道当時「双方は現在、正確な使用料を協議している」という状況だったため、それから半年以上経っており韓国軍に関しては折り合いがついたと思われる。今回の「サイバー攻撃」では韓国軍は被害を被っていない。

さて、今回の「サイバー攻撃」、韓国はどのように始末をつけるのか、興味は尽きない。