Synaptics Clickpad で右クリックを有効にする on Ubuntu Precise (12.04)

2本指タップで右クリックになるし、それに慣れちゃったけど、一応

オンラインで変更するには

$ xinput set-prop "SynPS/2 Synaptics TouchPad" "Synaptics Soft Button Areas" 3872 0 3984 0 0 0 0 0

数字はよそから拾ってきた適当なものだけど。
これで一応右下をクリックすると右クリック扱いとなる。

Skype4Py で bot を作る

コンタクトリストのユーザーがオンラインになったら「おかえり」というストーキング bot

ちなみに Ubuntu 12.04 (Precise) で Skype4Py (パッケージ名 python-skype) をインストールするにはレポジトリに ppa:skype-wrapper/ppa を追加する必要がある (パッケージ skype-wrapper.deb 自体はインストールする必要はない)。

# -*- coding: utf-8 -*-

import Skype4Py
import Queue
import logging

# Skype4Py のログを出力する (DEBUG だと大量なので注意; 便利だけど)
logging.basicConfig(level=logging.DEBUG)

# ALTER CHAT の expected response が変わったので修正
def my_Chat__Alter(self, AlterName, Args=None):
    return self._Owner._Alter('CHAT', self.Name, AlterName, Args,
                              'ALTER CHAT %s' % AlterName)
Skype4Py.chat.Chat._Alter = my_Chat__Alter


q = Queue.Queue()

def on_online_status(user, status):
    print '%s (%s): %s' % (user.Handle, user.FullName, status)
    if status == 'ONLINE':
        q.put(user.Handle)

def main():
    skype = Skype4Py.Skype()
    skype.OnOnlineStatus = on_online_status
    skype.Attach()

    while True:
        try:
            item = q.get(True, 86400)   # 0 にすると KeyboardInterrupt が効かない
            chat = skype.CreateChatWith(item)
            chat.SendMessage('おかえり')
            chat.Leave()
        except Queue.Empty:
            pass

main()

skypeグローバル変数にぶっこんで,on_online_status() から CreateChatWith() してもいいんだけど,やり取り等は別スレッドで動いているらしく,ちょいと気持ち悪かったので Queue でスレッド間通信することにした。おかげで while True: time.sleep(1) みたいなことをしなくてもよくなった (実質 Queue.get() してるので同じだけど)。
上のサンプルでは Queue にユーザーハンドルだけつっこんでるけど,将来的にはそれなりのコマンドオブジェクト等をつっこむようにしたほうがいいですね。

uWSGI でファイルが更新された時にリロードする

最初は inotifyx 使って自力で書こうとしてたんだけど,ブロックしてうまくいかなかったりして,thread でも立てなきゃいけないのかなと思って uwsgidecorators を読んでたら,そもそも uwsgidecorators (というかそもそも uwsgi-core) にファイル更新検知機能がついてた。

メインとなる wsgi ファイルが main.wsgi というファイル名だったとして,監視するモジュール((別モジュールだてにしなくてもいい (main.wsgi に入れ込む) のかもしれないけど追ってない追記: やってみたら別モジュールに独立させなくてもうまくいった。小さい捨て WSGI スクリプトであればリロードのロジックを入れ込むのが楽。(もちろん,WSGI アプリケーションとして独立させるなら入れ込まないほうがいいけど) )) watcher.py は:

# watcher.py
import uwsgi
from uwsgidecorators import filemon

@filemon('main.wsgi')
def reloaded(num):
    uwsgi.reload()

対応する uWSGI のパラメータは,たとえば:

; uwsgi.ini
[uwsgi]
master = true
plugins = python,http
http = :8000
wsgi-file = main.wsgi
import = watcher

ファイルがいっぱいあったらどうすんねーんと思うかもしれないけど,@filemon デコレータは,ディレクトリの監視もできます。

ちなみに内的には inotify 機構を使っているので無駄はないはず。IN_ALL_EVENTS レベルの監視をしてるんで,touch とかしてもちゃんとリロードされます。



あと今回の話と関係ないけど,たとえば 1 分間リクエストがなかった場合にワーカーを自動的に kill するには

idle = 60

のようにしておけばよい。uWSGI 使うようなシチュエーションでそんなにメモリをケチるシーンはないかもしんないけど。

追記: idle (や lazy) と @filemon の相性はよくないかもしんない。ワーカーがいない状態で編集してリクエスト投げたら編集前の内容だったことがあった。気のせいかもしれないけど。


もういっこわりと便利なオプションは harakiri

harakiri = 60

のようにしておくと,処理に 60 秒以上かかるワーカーは強制的に落とされます。

NLog で動的にログの出力状況を変更する

設定のオンオフで,ログを出力するかどうかを変えたり,出力先 (ファイルや TextBox など) を変更したい場合。

たとえば NLog.config が以下のようなときに

<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <targets>
    <target name="file" xsi:type="File" fileName="${basedir}/app.log"/>
  </targets>
</nlog>

target は定義されているけど rule は定義されていないので,このままではログはどこにも出力されていない。

この状況で上記の file target にログを出力するように変更するには,

using NLog;
using NLog.Config;

NLog.Targets.Target target = LogManager.Configuration.FindTargetByName("file");
LoggingRule rule = new LoggingRule("*", LogLevel.Debug, target);

LogManager.Configuration.LoggingRules.Clear();
LogManager.Configuration.LoggingRules.Add(rule);
LogManager.ReconfigExistingLoggers();

のようにする。

LogManager.Configuration.LoggingRulesList<LoggingRule> なので,List ジェネリックスのメソッドを使って操作すればよい。ただし操作しただけだとすでに生成されてしまっているロガーには (その設定変更が) 伝わらないので LogManager.ReconfigExistingLoggers() する必要がある。

Rack::Auth::Digest::MD5 のつかいかた

Rack::Auth::Digest::MD5opaque を渡さないといけない((つうかそもそも opaque は optional なはずなのに Rack::Auth::Digest::MD5 では必須パラメータってのも変なんだけど。))ので素直に書けないと思いがちだけど,現在の Rack::Auth::Digest::MD5 は第2引数に opaque をとるので,シンプルに use を使って書ける。

require 'rack/auth/digest/md5'

use Rack::Auth::Digest::MD5, 'my realm', '', do |username|
  'password'
end

run my_app

Padrino の場合はこんなふうに。

Padrino.before_load do
  require 'rack/auth/digest/md5'
  Padrino.use Rack::Auth::Digest::MD5, 'my realm', '', do |username|
    'password'
  end
end

アプリケーションのステートに応じて opaque を返したい場合((これが本来の opaque の使い方。とはいえ,いろいろ代替手段があるので (nonce に入れ込んじゃうとか Cookie 使うとか) opaque が真面目に使われるケースはないんじゃないかな。))は,結局 Rack::Auth::Digest::MD5インスタンスを生成して rack mount していくしかない気がする。そもそもそんなシチュエーションでは rack middleware じゃ単純には無理かな?


ともかく。
実は名前付きパラメータでも引数を渡せるので下記のようにも書ける。

require 'rack/auth/digest/md5'

use Rack::Auth::Digest::MD5,
  { 
    :realm  => 'the realm',
    :opaque => '',
  },
  do |username|
    'password'
  end

run my_app

ところで。
せっかく HTTP Digest 認証なのに,生パスワードを書かないといけないなんてダッサいと思いませんか。
:passwords_hashed パラメータを true にすると,ハッシュ化したパスワード等 (いわゆる A1) を渡せばよくなるのでシステムに生パスワードを保存しておく必要がなくなる((実はハッシュ化された A1 (と username の組) を盗まれると認証できてしまう。なのでハッシュ化して保存したとしてもユーザーの (他のサービスと共用しているかもしれない) 生パスワードが盗まれないという意義しかない。))。

require 'rack/auth/digest/md5'

REALM  = 'the realm'

require 'digest/md5'
PWHASH = Digest::MD5.new.update('%s:%s:%s' % ['dayflower', REALM, 'password'])

use Rack::Auth::Digest::MD5,
  { 
    :realm            => REALM,
    :opaque           => '',
    :passwords_hashed => true,
  },
  do |username|
    PWHASH
  end

run my_app

PWHASH の算出のところに生パスワード書いてあるけどこれはあくまでサンプルだからであって,あらかじめ計算しておくなり,htdigest コマンドで生成した値を利用するなり,データベースに保存しておくなり,しておけば生パスワードを保存しておく必要はなくなる。

Rack::Auth::Digest::MD5 での nonce のとりあつかい (とバグ)

一般に,HTTP Digest 認証でリプレイ攻撃を「厳密に」防ぐには

  • nonce をサーバサイドで生成しサーバに保持しておく
  • クライアントから返された noncenc の組がすでに認証済ならハネる (新しい nonce を生成し,stale を立ててレスポンスするだけでいい)
  • クライアントから返された nonce がサーバサイドに保持したものと違えばハネる (上記と同様 stale token とする)

などする必要がある。

Rack::Auth::Digest::MD5 では nonce はタイムスタンプになっている。のでサーバサイドに nonce を保持しておく必要がなくなり実装が楽である。
「一定時間」を超えた nonce を破棄していけば,その一定時間を超えた段階でのリプレイ攻撃が成立しなくなるのでカジュアルには,悪い選択肢ではない*1

Rack::Auth::Digest::MD5 ではデフォルトではこの「一定時間」が設定されていない。このことは nonce が破棄されないことをしめしている。なのでリプレイ攻撃やり放題である (サーバサイドに何も保持していないので nc のチェックもしてないし)。
「一定時間」を設定するには Rack::Auth::Digest::Nonce::time_limit に値を設定する。

require 'rack/auth/digest/nonce'

Rack::Auth::Digest::Nonce::time_limit = 10

単位は秒なので,この例でいくとサーバが nonce を発行してから10秒経つと無効な nonce となる。時間切れになった場合は,stale 属性の立った WWW-Authenticate をサーバが返すので,ユーザーエージェント側でパスワード入力ダイアログが再び出ることはない。エージェントが新しい nonce をもとに自動的に認証ダイジェストを計算して再送信することになる((なので time_limit をかなり小さくしてもまぁ大丈夫なのだが,サーバとクライアント間の通信遅延が大きい場合やクライアントの処理能力が非常に低い場合などはそれなりに大きなものにしておく必要があるだろう。))。

と,これでいいはずなんだけど,現在のところ Rack::Auth::Digest::Nonce にバグがあるので,このようにすると常に stale となり延々と認証リクエストレスポンスネゴシエーションが走ってしまう。このバグに対処するには下記のようにすればよい。

require 'rack/auth/digest/nonce'

class Rack::Auth::Digest::Nonce
  def stale?
    !self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit
  end
end

誰も使っていないフィーチャーなのだろう。そもそも安全でない経路で Digest 認証をやることがメジャーではないのかもしれない。

暇ができたら issue 立てて pull request するつもりだけど,テストまで込みで考えるとめんどいなぁ。pull request だしといた。master にマージされた

*1:とはいえ当然ながら「一定時間」内でのリプレイ攻撃は成立してしまうので,攻撃者が盗聴可能な経路でこのような実装を利用するのはやめたほうがよい。

Ubuntu 12.04 (Precise) のインストールではまったこと

普通にインストールするぶんにははまらないけど個人的事情ではまった部分です。
つまり備忘録。

LVM を有効にしたインストール

基本路線は Ubuntu 8.04 を LVM 有効にしてインストールする - daily dayflower のとおり。ただし,dm-mod はインストールする必要ない (だいぶ前からだけど)。
一点はまったのは,「インストールされた環境に lvm2 をインストールする」部分。
ネットワークブートな Live 環境からインストールしようとしたからかもしれないけど,chroot したあとにうまくネットにつながらなかった。

procfs をマウントしたり,Live 環境の /etc/resolve.conf をコピーする必要があった。

ubuntu@ubuntu:~$ sudo mount /dev/sda1 /target

ubuntu@ubuntu:~$ sudo mount -o bind /proc /target/proc

ubuntu@ubuntu:~$ sudo cp /etc/resolve.conf /target/etc/

ネットブートからのインストールだと,ここで /target/etc/network/interface をいじっといたほうがいいかも (eth0 な部分を削除するか dhcp に指定する)。

あとは元の手順通り。

ubuntu@ubuntu:~$ sudo chroot /target

root@ubuntu:/# apt-get update

root@ubuntu:/# apt-get install lvm2

root@ubuntu:/# exit

終了時は umount もよしなに。

ubuntu@ubuntu:~$ sudo umount /target/proc

ubuntu@ubuntu:~$ sudo umount /target

winbind 認証ログイン

基本的に winbind による Active Directory 認証 on Ubuntu 11.04 - daily dayflower のとおりなんだけど,Samba が 3.6.3 になってて設定ディレクティブ等がかわってたのではまった。

[global]
        workgroup = HOGE
        realm = HOGE.EXAMPLE.COM
        security = ADS
        obey pam restrictions = Yes

        algorithmic rid base = 10000
        template homedir = /home/%U
        template shell = /bin/bash

#       winbind separator = !
        winbind cache time = 60
        winbind use default domain = Yes
        winbind nss info = sfu:HOGE
        winbind refresh tickets = Yes
        winbind normalize names = Yes
        winbind enum users = Yes
        winbind enum groups = Yes

        idmap config * : backend = tdb
        idmap config * : range = 1000000-1999999
        idmap config DOMAIN : backend = rid
        idmap config DOMAIN : range = 10000-19999

idmap のデフォルトの backend 等は idmap config * : のように,ドメインごとの設定と統一感のある記述になった (古い書き方でも deprecated っていわれるだけだけど)。
あと,じっさいに使うドメイン向けじゃなくてデフォルトのを tdb idmapper 等の永続性のある backend に指定しておく必要があるみたい (BUILTIN sid とかのために((idmap_rid(8) のマニュアルのうけうり。)) )。この部分に一番はまった。

winbind ユーザも lightdm からログインする

ディスプレイマネージャが lightdm に変更されていて,こいつはデフォルトだと登録されたユーザしかログオンできない (ユーザ名は選択する)。このままだと,winbind や ldap なユーザはログインできない。
ユーザ名も自分で入力できるようにするためには lightdm の設定をいじる必要がある。
具体的には,/etc/lightdm/lightdm.conf[SeatDefaults] セクションに下記のような設定を付け足す。

[SeatDefaults]
greeter-show-manual-login=true

DataMapper でカスタムリレーション

DataMapper - Associations の「Customizing Associations」に書いてある。


User と Message というモデルがあり,Message には宛先が(複数)指定できる,とする。できるだけ規約ベースで書くと以下のようになる。

class User
  include DataMapper::Resource

  property :id, Serial

  has n, :message_receipients
  has n, :messages,
    :through => :message_receipients
end

class Message
  include DataMapper::Resource

  property :id, Serial

  has n, :message_receipients
  has n, :users,
    :through => :message_receipients
end

class MessageReceipient
  include DataMapper::Resource

  property :id, Serial

  belongs_to :user
  belongs_to :message
end

u = User.create
m = Message.new
m.users << u
m.save

p m.users
p u.messages

でもこれだと,「宛先」ぽさとか User の「受信したメッセージ」ぽさの意図がなくなってわかりにくい。

class User
  has n, :message_receipients,
    :child_key => [ :receipient_id ]
  has n, :received_messages, :model => 'Message',
    :through => :message_receipients,
    :via => :message
end

class Message
  has n, :message_receipients
  has n, :receipients, :model => 'User',
    :through => :message_receipients
end

class MessageReceipient
  belongs_to :receipient, :model => 'User'
  belongs_to :message
end

u = User.create
m = Message.new
m.receipients << u
m.save

p m.receipients
p u.received_messages

要するに :child_key とか (今回は使ってないけど) :parent_key とか :via などのオプションを使えば,リレーション時にどのような外部キーを使うかなどをカスタマイズできる。

新規開発だけでなくレガシースキーマを相手にする場合にも。

まぁ ORM でリレーションまでやってしまうかどうかという問題もあるけど。