2011年12月4日日曜日

システムデフォルト以外の Python での pyflakes

特定バージョンの Python を使用するために、Prefix を指定して手動でコンパイルした環境で vim から pyflakes を使用しようとしたのですが、以下のようなエラーが出てしまいます。
AssertionError: Vim must be compiled with Python 2.5 or higher; you have 2.4.3
手動コンパイルした Python は 2.7 で、virtualenv の環境から実行しているため、そちらを見に行くのかと思いましたが、どうもシステムデフォルト(/usr/bin/python)を見ている様子。
$ which python
~/.virtualenvs/foobar/bin/python
$ python -V
Python 2.7.2
ここを見ると、環境ごとに vim のビルドが必要(!)と書かれている。とりあえず、試してみる。
$ LD_LIBRARY_PATH=$HOME/.virtualenvs/foobar/lib
    PATH=$HOME/.virtualenvs/foobar/bin:$PATH \
    ./configure --enable-pythoninterp \
    --with-python-config-dir=$HOME/.virtualenvs/foobar/lib/python2.7/config \
    --prefix=$HOME/vim27

$ make install
ビルドした vim から pyflakes を使用してみるとエラーは出なくなっている。バージョンを確認しても手動でコンパイルした Python を見ているようだ。
:python import sys; print(sys.version)
2.7.2
vim から呼び出す Python は、コンパイル時に指定されているんですね。目的は果たせたけど、、他に方法はないものか。

2011年11月12日土曜日

hg rollback の dry-run オプション

最近、Mercurialを使い始めました。一度コミットをしたものはプロジェクトの歴史として残す、というのが基本ポリシーみたいなのですが、MqExtensionなどを使用すると色々と修正ができるようです。少しずつ馴染んできたのですが、作業を重ねた後にrollbackした際、どの時点に戻るのか分からなくなる時があります。ふとマニュアルを見ていると、こんなオプションが!
hg rollback

Options:
-n, --dry-run do not perform actions, just print output

これで、安心してrollbackを実行できそうです。

2011年10月23日日曜日

エディタ

普段は、Komodo Edit というエディタを使っていて、Eclipse ほど重厚でなく、ほどよいバランスが気に入っています。 

Emacs は難しそうというイメージがあって敬遠してたけれど、emacs-for-pythonというPython用のエクステンションを集めて、簡単に使えるようにしてくれるものがあると知り、試してみました。 

導入はとても簡単で、ダウンロードしたファイルを適当なパスに保存して、必要なパッケージを入れ、設定ファイルに一行書くだけ。例えばUbuntu だと、こんな感じ。
$ sudo aptitude install pyflakes pymacs
$ vi ~/.emacs
---------------------
(load-file "~/.emacs.d/emacs-for-python-0.2.1/epy-init.el")
---------------------

IDEのように補完ができるようになります。

少しだけ悩んだのが、最初に一度だけ .ropeproject を作成しないとAuto Completeのウインドウが出てこないところ。 

補完のために Meta + / すると、保存する場所を聞かれるので指定してEnter。

確認が入るので、y を指定すれば、完了。

簡単に導入できて、便利に使えそうです。 

2011年9月17日土曜日

Apache 2.2.21 リリース

Apacheの脆弱性(CVE-2011-3192)に対応した 2.2.20 からまもなく 2.2.21 がリリースされています。 2.2.20 で行った修正に対する改善や、新たにMaxRangesディレクティブの追加などが含まれている模様。 

そもそも問題となっている Range ヘッダは、ブラウザでPDFの閲覧をしている時などにも使用されています。
GET /net/apache//httpd/docs/httpd-docs-2.2.14.en.pdf HTTP/1.1
Accept: */*
Range: bytes=3653554-3854257, 1077245-3603942, 3608039-3653553
...

このヘッダを使用して、なぜプロセスの肥大化が引き起こされるのかについては下記のページで丁寧に説明されていて、Range で細かい区間を要求されることにより、管理データを追加していくループ処理の増加が原因とのこと。

  CVE-2011-3192 Range header DoS vulnerability Apache 1.3/2.x の更に続き

追加された MaxRanges ディレクティブ では、制限値(デフォルト: 200)を超えた場合、要求されたコンテンツをそのまま返すようになっています。区間要求が無視されることにより、プロセスの肥大化を防ぐ仕組みですね。
# vi httpd.conf
----------------------
# MaxRanges: Maximum number of Ranges in a request before
# returning the entire resource, or 0 for unlimited
# Default setting is to accept 200 Ranges
MaxRanges 5
----------------------

// 制限内の数を指定した場合
GET / HTTP/1.1
Host: localhost
Range: bytes=0-1,1-2,2-3,3-4,4-5 <= 一度に複数の範囲を指定

HTTP/1.1 206 Partial Content <= 206 レスポンスが返る
...
Accept-Ranges: bytes
Content-Length: 412
Content-Type: multipart/byteranges; boundary=4ad1ca44eeaa22

// 制限を超過した場合
GET / HTTP/1.1
Host: localhost
Range: bytes=0-1,1-2,2-3,3-4,4-5,5-6

HTTP/1.1 200 OK <= 要求されたコンテンツをそのまま返している
...
Accept-Ranges: bytes
Content-Length: 44
Content-Type: text/html

2011年9月1日木曜日

PyPy を試してみる

ときどき Project Euler の問題をしているのですが、そのまま書いてしまったため、結果の出力にすごく長い時間のかかるコードをふと PyPy で実行してみると、あまりの速度にびっくり。
#!/usr/bin/env python

result = 0
i = 20
while True:
    for num in range(1, 21):
        if not i % num == 0:
            break
    else:
        result = i
    if result:
        break
    i += 1
print result

$ time pypy problem5.py 
real    0m35.395s
user    0m34.910s
sys     0m0.090s

$ time python problem5.py 
real    5m19.202s
user    5m15.520s
sys     0m0.070s
テストではないけれど、PyPy のすごさを垣間見た気分。

2011年8月19日金曜日

ビット演算って

どういった時に使用するのか、よく分からなかったビット演算。MACアドレスが正しいフォーマットか調べる必要があって、ここのコードを参考にしていた時に、こういう使い方があるのかと思ったのでメモ。
def _mac2int(addr, sep=_MACsep):
     # convert a MAC str to an int
    h = addr.split(sep)
    if len(h) > 4:
        i = 0
        for b in h:
            b = int(b, 16)
            if 0 <= b < (1<<8):
                i = (i << 8) | b
            else:
                break
        else:
            if 0 < i < (1<<48):
                return i
    raise ValueError('invalid MAC address: %r' % (addr,))
MACアドレスの文字列を int に変換している関数で、失敗すると ValueError になるようになっています。 その中でビット演算が使用されているのですが、分かりやすくするために'FF:FF'を変換するとした場合、まず左側の'FF'を int に変換します。
>>> int('FF', 16)
255
>>> bin(255)
'0b11111111'  <= 2進数にするとこうなる
次に、最初に変換した部分を 8bit 左にずらして、同様に次の'FF'を int に変換し、ビットOR をとる。
>>> 255 << 8
65280
>>> bin(65280)
'0b1111111100000000'
>>> bin(255)
'0b11111111'
>>> 65280 | 255
65535
>>> bin(65535)
'0b1111111111111111'
>>> bin(0xFFFF)
'0b1111111111111111'
結果、0xFFFF を int に変換したのと同じになります。MACアドレスは 6オクテットなので、これを6回繰り返すとMACアドレスを int に変換できるんですね。勉強になりました。

2011年8月6日土曜日

ヒープソート

C言語のアルゴリズム本を見ながら、Pythonにて書き換えてみる。
ヒープソートはこんな感じ。
def downheap(buff, parent, bottom):
    tmp = buff[parent]

    while True:
        cl = parent * 2 + 1
        cr = cl + 1

        if cl >= bottom:
            break
        if cr < bottom and buff[cl] < buff[cr]:
            cl += 1
        if tmp > buff[cl]:
            break

        buff[parent] = buff[cl]
        parent = cl
    buff[parent] = tmp


def heapsort(buff):
    size = len(buff)
    for i in range(size / 2 - 1, -1, -1):
        downheap(buff, i, size)

    for j in range(size - 1, 0, -1):
        tmp = buff[j]
        buff[j] = buff[0]
        buff[0] = tmp

        downheap(buff, 0, j)

if __name__ == '__main__':
    data = [2, 4, 6, 8, 10, 1, 3, 5, 7, 9]
    heapsort(data)
    print data

書き換えながら、ループを抜ける判定条件に悩む。どうして'A >= B'になるのか理解できなかったけれど(AとBがイコールになることはあるの?)、デバッガの助けを借りて解決。デバッガ便利ですね。
$ pdb heap.py 
> /tmp/heap.py(4)()
-> def downheap(buff, parent, bottom):
(Pdb) b 12, cl == bottom
Breakpoint 1 at /tmp/heap.py:12
(Pdb) r
> /tmp/heap.py(12)downheap()
-> break
(Pdb) p parent, cl, bottom
(3, 7, 7)
(Pdb) c
> /tmp/heap.py(12)downheap()
-> break
(Pdb) p parent, cl, bottom
(0, 1, 1)

2011年7月18日月曜日

collectionsモジュール

EuroPython 2011 のレポートを見て知ったのですが、python 2.7 で Counter というクラスが追加されているんですね。

リスト内の要素数をカウントしたい時、こんな感じで書いていましたが、
>>> cnt = {}
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
...     cnt[word] = cnt.get(word, 0) + 1
...
>>> cnt
{'blue': 3, 'green': 1, 'red': 2}

Counter を使うとこんな感じで書けます。
>>> from collections import Counter
>>> cnt = Counter()
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
...     cnt[word] += 1
... 
>>> cnt
Counter({'blue': 3, 'red': 2, 'green': 1})

すっきりしていて、いいですね。

2011年7月3日日曜日

curl がすごく便利

名前は聞いた事があるけれど、使った事がなかったコマンド。
(いまさらだけど)Webアプリケーションの脆弱性チェックなどで、すごく便利だと実感。

チェックしたいWebアプリケーションにPOSTリクエストを色々変えながら、レスポンスを確認したいときに Paros のようなProxyを使うけれど、curl だとコマンドラインで操作できるので、ターミナル上でも手軽に実行できるし、同じことを繰り返す場合も便利。

ログイン画面などで、テストする場合はこんな感じ。

$ curl http://foo.co.jp/login -c cookie.txt
セッションIDがCookieに保存される場合は、一度ログインページを表示してCookieの内容をファイルに保存。

$ curl -v http://foo.co.jp/login \
    -d username=guest -d password=guest \
    -b cookie.txt -L
そして、POSTを送ってログインを試みる。フォームの送信は -d オプションで指定。また、先ほど取得したCookieを -b オプションで一緒に送信。POSTが成功した場合、リダイレクトで別のページに遷移することが多いので、-L を指定しておくとブラウザの動作に近くなります。HTTPのリクエスト/レスポンスが見たい場合は -v も指定。

あとは、SSLのページでも Ubuntu などでパッケージから curl をインストールすると主要な証明書も一緒にインストールされているから問題なし。
これから、どんどん使っていこう。

2011年6月15日水曜日

Net-SNMP viewの設定

snmpd.conf の view 設定として、下記が "全て公開" になるというサンプルがありますが、なぜそうなるのか分からなかった(特にMASK部分)ので、調べてみました。
##           incl/excl  subtree                     mask
view all    included   .1                             80

まずは 'man snmpd.conf'。mask の指定は16進数ということは分かったけれど、まだハッキリしない。
view VNAME TYPE OID [MASK]
...
  MASK is a list of hex octets (optionally separated by '.' or ':') 
  with the set bits indicating which   subidentifiers in the view 
  OID to match against.

ここのメーリングリストの説明を見て、やっと分かった!












つまり、ビットが立っている部分は固定(指定したOIDそのまま)で、立っていない部分はワイルドカードの扱いになると。実際に確認してみよう。

たとえば、このようなインターフェース構成のサーバで、"全て公開"の設定にしていると、全てのインターフェースの情報が見える(当然ですね!)
# snmpwalk -v 2c -c private localhost .1.3.6.1.2.1.2.2.1.2
IF-MIB::ifDescr.1 = STRING: lo
IF-MIB::ifDescr.2 = STRING: eth0
IF-MIB::ifDescr.3 = STRING: eth1
IF-MIB::ifDescr.4 = STRING: eth2

(「受信した総バイト数」を表示)
# snmpwalk -v 2c -c private localhost .1.3.6.1.2.1.2.2.1.10
IF-MIB::ifInOctets.1 = Counter32: 1270274
IF-MIB::ifInOctets.2 = Counter32: 165686032
IF-MIB::ifInOctets.3 = Counter32: 9656051
IF-MIB::ifInOctets.4 = Counter32: 635738

(「送信した総バイト数」を表示)
# snmpwalk -v 2c -c private localhost .1.3.6.1.2.1.2.2.1.16
IF-MIB::ifOutOctets.1 = Counter32: 1270274
IF-MIB::ifOutOctets.2 = Counter32: 19914854
IF-MIB::ifOutOctets.3 = Counter32: 805032
IF-MIB::ifOutOctets.4 = Counter32: 8933886

ここで、このように設定を変更すると、
view ifRow4 included .1.3.6.1.2.1.2.2.1.0.4  0xff:a0
4番目のインターフェース(eth2)の情報しか見えなくなる。
# snmpwalk -v 2c -c private localhost .1.3.6.1.2.1.2.2.1.10
IF-MIB::ifInOctets.4 = Counter32: 635738

# snmpwalk -v 2c -c private localhost .1.3.6.1.2.1.2.2.1.16
IF-MIB::ifOutOctets.4 = Counter32: 8933886
公開する情報を、特定のインターフェースに制限しつつ、様々な IfEntry を取得したい時などに便利そうですね。

まとめると、下記の設定はMIBのオブジェクトツリーのトップ('iso(1)')にのみビットが立っているので、配下のオブジェクトは任意 = 全て公開 ということになるんですね。
.1
 1 0 0 0 0 0 0 0   = 0x80

2011年6月1日水曜日

Python の hash関数

Google Code University に Gruyere というデモサイトを使って、Webアプリケーションの脆弱性と対策を説明しているガイドがあります。

少しずつ読み進めているのですが、cookie処理の部分について少し調べてみました。

gruyere.py (601行目)
def _CreateCookie(self, cookie_name, uid):
    ....
    h_data = str(hash(cookie_secret + c_data) & 0x7FFFFFF)

発行するcookieにハッシュ値を付与している部分ですが、「Pythonのhash関数はセキュアでないため、"違う文字列で同じハッシュ値" を短い時間で発見できる」と説明されています。

そうすると cookie_secret を知らなくても、cookieの改ざんができてしまうため、この用途にhash関数は向いていないとのこと。対策として「セキュアなhash関数を使用する」とあるので、hashlib などを使用するべきなのかな。

そもそも、Pythonのhash関数は dict での使用に適するようにされていて、ランダムよりも(末尾の文字がインクリメントされるような)よく似たケースにおいて規則的な変化をする性質を重視していると dictobject.c のコメントに書かれているんですね。

python/Objects/dictobject.c
/*
Major subtleties ahead:  Most hash schemes depend on
having a "good" hash function, in the sense of simulating
randomness.  Python doesn't:  its most important hash
functions (for strings and ints) are very regular in
common cases:
'''
>>> map(hash, (0, 1, 2, 3))
[0, 1, 2, 3]
>>> map(hash, ("namea", "nameb", "namec", "named"))
[-1658398457, -1658398460, -1658398459, -1658398462]

また、Pythonのhash関数はFNVというアルゴリズムに近いものだそうだ。

FNV-1 hash algorithm
hash = FNV_offset_basis
for each octet_of_data to be hashed
    hash = hash  FNV_prime
    hash = hash XOR octet_of_data

python/Objects/stringobject.c
string_hash(PyStringObject *a)
{
    ...
    x = *p << 7;
    while (--len >= 0)
        x = (1000003*x) ^ *p++;

この辺りがそうなのかな。
きちんと理解できるように、がんばらねば。