Exim4でメールアドレスを量産する

EメールアドレスをGMailのようにサフィックスを付けても通るようにしたかったのでちょっとやってみました。
GMailのように '+' を使うルールだとこれを弾くサービスが増えてきたので、エイリアスの区切りには怪しくないように '.' を使うことにします。例えば

user@hoge.com
user.fuga@hoge.com
user.fuga.piyo@hoge.com

というメールアドレスはぜんぶ user 宛に届くようにします。

以下はDebian Wheezyでの方法です。Exim4を選んだのはDebianを入れると標準でインストールされるから。

Exim4の設定

前提として、インターネット上からメールを受け取れるようにする設定は既に済ませてあるものとします。

それに加えて /etc/exim4/conf.d/router/ 以下に 399_exim4-config_dot_alias を作成するだけです

dot_aliases:
	driver = redirect
	condition = ${if match{$local_part}{\.} {true} {}}
	data = ${sg {$local_part}{\N\..*$\N}{}}@$domain

これで /etc/init.d/exim4 restart すれば完了。エイリアスが欲しいだけならこれで終わりです。

振り分け

本当はこっちがやりたかった事。メールアドレスの量産の他に、自動的に規則的な階層を持つような振り分けをしたかったので以下のようにしました。

方針
  • メールフォーマットにMaildirを使う
  • 振り分けにはprocmailを使う
  • MDADovecotを使い、メール取得にはIMAP4を使うようにする

振り分けルール以外は一般的なMaildir形式の設定をするだけ。

エイリアスに '.' を選んだのは、RFCのメールアドレスでの '.' の扱い方の法則がDovecotにおけるMaildir++の階層の表現方法にとって都合が良く扱いやすかったからというのもあります。

Exim4

Maildir方式に変更。

/etc/exim4/update-exim4.conf.conf の dc_localdelivery= を変更

dc_localdelivery='maildir_home'
Dovecot

dovecot-imapdをインストール。

Maildir形式への変更。

/etc/dovecot/conf.d/10-mail.conf の mail_location の行

mail_location = maildir:~/Maildir
procmail

~/.procmailrc に追加。local-partの '.' の後を切り出してMaildir方式で .INBOX.hoge.fuga/ という名前で出力するという内容

MAILDIR=$HOME/Maildir/
DEFAULT=$MAILDIR
USER=`id -un`

:0
* $ ^To:.*\<$USER\/\..*@
{
dir=`echo "$MATCH" |sed 's/@.*//;s/^/.INBOX/'`
:0
$dir/
}

これでメールボックスにIMAP4でアクセスすると、メールアドレスから作られた階層が見えるようになっていると思います。

シェルスクリプトで乱数を扱う

シェル組込変数 RANDOM

bashzshなど一部のシェルでは特殊な変数 RANDOM があり、参照する毎に範囲の小さい整数を得ることが出来ます。

$ echo $RANDOM
30940
$ echo $((RANDOM % 100))
22

jot(1)

jot(1)は連番や乱数を出力するプログラムです。出力回数や範囲を指定出来るのでなかなか使い出はあります。deb系では athena-jot パッケージをインストールします。

$ jot -r 1 10 99
72
$ jot -r 10 1000 9999
8881
2834
3537
5889
7430
9962
4905
9548
3575
9762

awk

awkの関数 rand() で0–1の範囲の実数の乱数を扱えます。srand() で乱数の初期化することを忘れずに。

$ awk 'BEGIN{srand();print int(rand() * 100)}'
21

/dev/urandom

urandomデバイスを読むことで文字通りランダムなバイト列を得ることが出来ます。そのままでは扱えないのでodで出力を加工します。

これで得られる整数は範囲が2の冪乗になるので、必要なら剰余を取るなどしてさらに加工します。$(()) を使った数値計算POSIX標準で使えるのでexprよりはこれを使う事をおすすめします。

$ od -vAn -tu2 -N2 </dev/urandom
 22541
$ echo $((`od -vAn -tu2 -N2 </dev/urandom` % 1000))
104
$ od -vAn -tu4 -N16 </dev/urandom
  495822970   28206112 1470428861  335315840
$ ( for val in `od -vAn -tu1 -N8 </dev/urandom` ; do echo $((val % 100)) ; done )
48
0
75
37
55
24
87
50
$ od -vAn -tx1 -N16 </dev/urandom |tr -d '[:space:]' |sed -e 'a\'
722b6a021c13873512c5587b176e4b63
$ dd if=/dev/urandom bs=24 count=1 2>/dev/null |base64
SxaXbo5BP/El3MWMhaFD/42k0N1g00gc

rand(1ssl)

opensslでもランダムなバイト列を生成することが出来、/dev/urandom の代わりに使えます。

普通このコマンドは -base64 オプションと合わせてナンス生成等に使う事が多いと思います。

$ openssl rand 4 |od -vAn -tu4
 3671196097
$ openssl rand -base64 24
TqJAtoQ6Xlho56DvnnEkuV29mqKz8mQb

shuf(1)

shufは入力された行をランダムに入れ替えて出力します。乱数とは少し毛色が違いますが一応。

$ shuf <<heredoc
> aaa
> bbb
> ccc
> ddd
> eee
> fff
> heredoc
fff
eee
ccc
bbb
aaa
ddd

Linuxでデスクトップをライブ配信する

Ustreamニコニコ生放送Linuxのデスクトップを配信するのに必要な物・方法のメモです。

必要な物を用意する手順はDebian Lennyでの環境を元に書いてありますので、適宜読み替えて下さい。

必要な物

ffmpeg

ffmpegでデスクトップのキャプチャが出来ます。

$ ffmpeg -version

してffmpegの情報を表示し、ビルド設定に

 --enable-x11grab

が含まれていればキャプチャ機能が有効になっています。無い場合はこのオプションを付けてffmpegの再ビルドが必要です。debian-multimediaffmpegでは有効になっています。

mjpegtools_yuv_to_v4l

ffmpegでキャプチャしたデータをvloopbackが受け取れるように変換するために使います。

ソースを http://panteltje.com/panteltje/mcamip/mjpegtools_yuv_to_v4l-0.2.tgz からダウンロードし、そのままビルド・インストールします。ビルド前にはMakefileのCFLAGSを編集して最適化オプションなどを変更すると良いでしょう。

vloopbackカーネルモジュール

vloopbackカーネルモジュールはキャプチャソフトからデータを受け取り、それをビデオデバイスとして出力させるために必要です。

ソースはvloopback-sourceとしてパッケージにまとめられています。Lennyには無いのでパッケージを直接ダウンロード・またはsid等から借りてきてこれをインストールします。

ソースをインストールしたら

# m-a a-i vloopback

でモジュールのビルド・インストールが完了します。

配信手順

まずはvloopbackをカーネルに追加してビデオデバイスを作成します。

# modprobe vloopback pipes=1

dmesgでvloopbackが作成したデバイスを確認します。

[23979.234319] [vloopback_init] : video4linux loopback driver v1.3
[23979.241036] [vloopback_init] : Loopback 0 registered, input: video0, output: video1
[23979.241045] [vloopback_init] : Loopback 0 , Using 2 buffers

この例では /dev/video0 が入力、/dev/video1 が出力として作成されています。

次に、デスクトップをキャプチャしてデータをデバイスの入力に送ります。

$ ffmpeg -f x11grab -s 512x384 -r 10 -i :0.0+100,200 -pix_fmt yuv420p -f yuv4mpegpipe - \
    |mjpegtools_yuv_to_v4l /dev/video0

-f x11grab は入力にXの画面のキャプチャを使うことを意味します。-i で画面とキャプチャ位置オフセットを、-s でキャプチャサイズを指定します。キャプチャデータは mjpegtools_yuv_to_v4l で変換してビデオデバイスに流しています。他のオプションや詳細についてはffmpegのドキュメントを参照して下さい。

これで今 /dev/video1 を他のウェブカムと同じようにビデオデバイスとして扱えるようになりました。

/dev/video1 の出力の確認のためにビデオデバイスを表示するソフトを起動してみます。ここでは xawtv を使います。

$ xawtv -geometry 512x384 -c /dev/video1

これでデスクトップが表示されていればOKです。

xargs で -0 を使わずに find の出力を渡す

POSIXの xargs には -0 オプションがないので find で得たファイル名に空白などがあった場合に面倒が生じます。そのため、find の出力を加工します。

註: あまり試してない

$ find .//. -print \
    |sed -n ':loop;s/./\\&/g;${p;q};x;n;/\/\//!{x;s/.*/&\\/;x};x;p;x;b loop' \
    |xargs -E "" command... 

「find ... -print0 |xargs -0」の時と変えることは、パスの先頭に「.//」を付けること、find と xargs の間に sed スクリプトを挟むことです。

find が出力するディレクトリの区切りは「/」1個だけというのを利用し、「今の行に『//』が含まれていない場合は前の行の続きだから前の行の最後に『\』をつける」という処理を施しています。

処理は http://programming.itags.org/unix-linux-programming/82599/ の stephane_chazelas 氏の投稿を参考にしました。awk が使われていますが、ここでしていることと同じです。

sedスクリプトの解説はめんどいというか、自分でも苦戦したのでパスで……。

xargs で空白・改行・クオーテーションが入った引数を安全に渡す

xargs で空白が入った引数を渡す場合、「'」か「"」で引数を囲む必要があります。しかし、これらでくくった場合のエスケープシーケンスが全く提供されていないため、引数にさらに同じクオーテーションが含まれていた場合に問題があります。また、改行を入れることも出来ません。

多くのシステムでは、引数の区切りにヌル文字を使い、xargs に -0 オプションを付けてファイルを渡すことで解決する方法がありますが、POSIX標準ではこの方法を使うことが出来ません。

「'」か「"」でくくらない場合、「\」の後の文字(改行含む)はそのまま扱う事が出来ます。これを利用し、引数の文字全てと行末に「\」を付け、最後の行の「\」を取り除くという操作をすることで表題を解決出来ます。

printf "%s\n" "$arg" |sed 's/./\\&/g;s/.*/&\\/;$s/\\$//' >>args

ただし、この方法では空文字列を渡すことが出来ないので、空文字列を使うおそれがある時は事前にチェックをして「''」と出力する必要があります。

xargs はデフォルトで「_」が来たら終了してしまうので、-E に空文字列を設定して無効化します。

xargs -E "" command... <args

シェルスクリプトでエポック秒を得る

POSIXのシェルユーティリティにはエポック秒を直接出力するコマンドがないので生成する方法を考えます

perlを使う

POSIX非標準。perlぐらいどの*nixにもインストールされているしこれが普通。

$ perl -e 'print time, "\n"'

date(1)でフォーマット指定を使う

POSIX非標準。date(1)のフォーマット指定にエポック秒を出力する s があります。これはGNU拡張。

$ date +%s

日付時刻からエポック秒を計算する

全てPOSIXのコマンドだけでやろうとすると多分これしかないです。

エポック秒は次の計算式で算出出来ます。

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
    (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
    ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400
http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap04.html#tag_04_14

というわけで、dateとawkを組み合わせて次のような感じになりました。日時出力時はUTCを指定するのを忘れない。

$ date -u '+%Y %j %H %M %S' |awk '{
	y = $1 - 1900;
	d = $2 - 1;
	t = $5 + $4*60 + $3*3600 + d*86400 + \
		(y-70)*31536000 + int((y-69)/4)*86400 - \
		int((y-1)/100)*86400 + int((y+299)/400)*86400;
	print t;
}'

Cで書く

エポック秒なんて time_t の値そのものなんだからバイナリ使えるなら time(2) 出力するだけでいいよねみたいな。なお、time_t を出力する時は intmax_t でキャストするのが良いらしいです。

#include <stdio.h>
#include <time.h>
#include <stdint.h>

int main(void) {
    printf("%jd\n", (intmax_t)time(NULL));
    return 0;
}