昨今の円安のため香港ドルが高い (>_<)。 昨年は 10円/HK$ 台だったのに、いまや 13円/HK$ を超えている。 香港 (に限らないが) の人たちが大挙して日本に買物に来る気持ちが分かる。 これだけ円安が進めば、 日本じゅうどこへ行っても、 全てのものが安く感じられるのだろう。
逆に、 日本人が香港へ行くと、全てのものが以前より 3割ほど高く見えるわけで、 物欲が萎えてしまいほとんど買物しなかった (おまけに、香港に行く直前に日本で nexus 5 を買ってしまったし)。 とはいえ、 香港までわざわざ行っておきながら何も買わないというのもアレなので、 Qi 充電器を買ってみた。 深水埗 黄金電腦商場 地下35號舖 Sunny Computer Digital Co 力生電腦數碼公司 →
HK$199 = 約2700円なので、 ちっとも安くない。 「おにぎり」 こと、 ワイヤレスチャージャー 03 なら 2200円くらいで売っているが、 「おにぎり」 の電源が専用アダプタであるのに対し、 これは汎用の micro USB ケーブルが使えるのでよしとしよう。
この Qi 充電器を USB ハブの余っているポートにつないで机の上に置き、 その上に nexus 5 を置く。 使ってみると想像以上に便利。 電話がかかってきたとき、 以前はいちいち USB ケーブルを抜いていたのだけど、 Qi 充電器ならサッと nexus 5 を手に取れるし、 電話が終わったら Qi 充電器の上に戻すだけ。 はやく全てのケータイが Qi に対応して、 喫茶店などのテーブルに Qi 充電器を標準装備して欲しいなどと思う今日このごろ。
充電がワイヤレスなのに、 PC との通信に USB ケーブルを使っていては片手落ちである。 私は普段 rsync を使って nexus 5 上のデータを丸ごと PC へバックアップしているが、 有線な adb (Android Debug Bridge) を使うのは止めて Wi-Fi を使うことにした。
ここで注意したいのは、 主導権を握る (コントロールする) のは PC 側でなければならない、 ということ。 スマホ側 (nexus 5) が主導して rsync を起動するアプリならすでにいくつか出回っているが、 PC が目の前にあるときに何が嬉しくてスマホの小さい画面をいじらなきゃならないのかと思う。 Qi 充電器の上に置いたら、 もう 1 タッチといえどスマホには触りたくない。 全ての作業は PC のキーボードで完結させたい。
つまり、 PC がクライアントとなり、 スマホをサーバとして扱いたい、 ということ。 サーバのキーボードやモニタはトラブル発生時でもなければ使わないのと同様、 家にいるときはスマホは充電器の上に置きっぱなしにしておきたい。 あるいは寝室専用になってしまっているスマホ (各部屋に一台以上、専用スマホ/タブレットが置きっぱなしにしてある) は、 寝室に置きっぱなしのままで (別の部屋の) PC から操作したい。 スマホを PC から操作できれば、 コマンド一発で、 家じゅうのスマホ (10台くらいある) をいっぺんにバックアップしたり、 相互にデータを同期させたり、 何でも思いのまま (^^)。
スマホをサーバとして扱うには、 スマホ上で ssh サーバを動かしておけばよい、 ということで早速 dropbear サーバ (軽量 ssh サーバ) と rsync を nexus 5 (だけでなく私が持っている全ての android スマホ) にインストールした。
以下、インストールのメモ:
dropbear 公式サイトから最新版 2013.62 をダウンロードしてきてビルド。 dropbear は元々 android 用にビルドすることを想定している (つまり configure に 「--host=arm-linux-androideabi」 オプションを指定できる) ので楽。
私の PC の /usr/local/src/android-4.4_r1 以下には kitkat のソースツリーが置いてあるので、 これに含まれる android NDK と gcc クロスコンパイラ arm-linux-androideabi-gcc を使ってビルドした。
esaka:dropbear-2013.62 $ TOP=/usr/local/src/android-4.4_r1 esaka:dropbear-2013.62 $ SYSROOT=$TOP/prebuilts/ndk/current/platforms/android-9/arch-arm esaka:dropbear-2013.62 $ export CFLAGS=--sysroot=$SYSROOT esaka:dropbear-2013.62 $ export LDFLAGS=--sysroot=$SYSROOT esaka:dropbear-2013.62 $ export PATH=$TOP/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/etc:/usr/local/bin:/usr/local/sbin esaka:dropbear-2013.62 $ ./configure --host=arm-linux-androideabi --disable-zlib --disable-largefile --disable-loginfunc --disable-shadow --disable-utmp --disable-wtmp --disable-utmpx --disable-wtmpx --disable-pututline --disable-pututxline --disable-lastlog checking for arm-linux-androideabi-gcc... arm-linux-androideabi-gcc checking whether the C compiler works... yes checking for C compiler default output file name... a.out ... (中略) ... checking for crypt in -lcrypt... no configure: Disabling zlib configure: Disabling PAM configure: Using openpty if available checking for library containing openpty... no ... (中略) ... config.status: creating Makefile config.status: creating libtomcrypt/Makefile config.status: creating libtommath/Makefile config.status: creating config.h configure: configure: Using bundled libtomcrypt and libtommath configure: configure: Now edit options.h to choose features. esaka:dropbear-2013.62 $ make cd libtomcrypt && make make[1]: Entering directory `/usr/local/src/dropbear-2013.62/libtomcrypt' ... (中略) ... arm-linux-androideabi-gcc --sysroot=/usr/local/src/android-4.4_r1/prebuilts/ndk/current/platforms/android-9/arch-arm -o dropbear dbutil.o buffer.o dss.o bignum.o signkey.o rsa.o dbrandom.o queue.o atomicio.o compat.o fake-rfc2553.o ltc_prng.o ecc.o ecdsa.o crypto_desc.o gensignkey.o gendss.o genrsa.o common-session.o packet.o common-algo.o common-kex.o common-channel.o common-chansession.o termcodes.o loginrec.o tcp-accept.o listener.o process-packet.o common-runopts.o circbuffer.o curve25519-donna.o svr-kex.o svr-auth.o sshpty.o svr-authpasswd.o svr-authpubkey.o svr-authpubkeyoptions.o svr-session.o svr-service.o svr-chansession.o svr-runopts.o svr-agentfwd.o svr-main.o svr-x11fwd.o svr-tcpfwd.o svr-authpam.o libtomcrypt/libtomcrypt.a libtommath/libtommath.a svr-authpasswd.o:svr-authpasswd.c:function svr_auth_password: error: undefined reference to 'crypt' collect2: error: ld returned 1 exit status make: *** [dropbear] Error 1 esaka:dropbear-2013.62 $
ありゃ、エラーで止まってしまった。 「error: undefined reference to 'crypt'」 すなわち、 android には crypt(3) が無い、ということらしい。 パスワード認証なんてもともと使う気はない (ssh を使うなら公開鍵認証が基本!) ので、 options.h をちょっと修正:
--- options.h~ 2013-12-03 22:39:15.000000000 +0900 +++ options.h 2013-12-11 18:15:14.123522720 +0900 @@ -192,7 +192,7 @@ * PAM challenge/response. * You can't enable both PASSWORD and PAM. */ -#define ENABLE_SVR_PASSWORD_AUTH +//#define ENABLE_SVR_PASSWORD_AUTH /* PAM requires ./configure --enable-pam */ /*#define ENABLE_SVR_PAM_AUTH */ #define ENABLE_SVR_PUBKEY_AUTH @@ -314,7 +314,7 @@ #define DEFAULT_IDLE_TIMEOUT 0 /* The default path. This will often get replaced by the shell */ -#define DEFAULT_PATH "/usr/bin:/bin" +#define DEFAULT_PATH "/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin:/usr/bin:/bin" /* Some other defines (that mostly should be left alone) are defined * in sysoptions.h */
ついでに、 ssh でログインしたときのデフォルト PATH を android 向けに修正 (/system/bin などを追加)。
android での標準的なシェルは /system/bin/sh であるが、 getusershell(3) は正しい値を返してくれない。 kitkat のソース $TOP/bionic/libc/bionic/stubs.cpp には次のように定義されている:
static void unimplemented_stub(const char* function) { const char* fmt = "%s(3) is not implemented on Android\n"; __libc_format_log(ANDROID_LOG_WARN, "libc", fmt, function); fprintf(stderr, fmt, function); } #define UNIMPLEMENTED unimplemented_stub(__PRETTY_FUNCTION__) ... (中略) ... char* getusershell() { UNIMPLEMENTED; return NULL; }
単に一言、 /system/bin/sh と返してくれればいいものを、 なんで 「not implemented」 のまま放置しているのだろう? (というか実装しないなら libc 内で関数を定義しないで欲しい。 未定義なら configure が判断できるのに) と思いつつ、 dropbear のソースで getusershell(3) を呼び出しているところを (goto 文で ^^;) スキップさせる:
--- svr-auth.c~ 2013-12-03 22:39:15.000000000 +0900 +++ svr-auth.c 2013-12-11 18:10:20.383300542 +0900 @@ -291,6 +291,7 @@ * should return some standard shells like "/bin/sh" and "/bin/csh" (this * is platform-specific) */ setusershell(); + goto goodshell; while ((listshell = getusershell()) != NULL) { TRACE(("test shell is '%s'", listshell)) if (strcmp(listshell, usershell) == 0) {
ヘッダファイル options.h を書き換えたので、 念のため 「make clean」 してからビルドし直し:
esaka:dropbear-2013.62 $ make clean cd libtomcrypt && make clean ... (中略) ... esaka:dropbear-2013.62 $ make cd libtomcrypt && make make[1]: Entering directory `/usr/local/src/dropbear-2013.62/libtomcrypt' ... (中略) ... arm-linux-androideabi-gcc --sysroot=/usr/local/src/android-4.4_r1/prebuilts/ndk/current/platforms/android-9/arch-arm -o dropbear dbutil.o buffer.o dss.o bignum.o signkey.o rsa.o dbrandom.o queue.o atomicio.o compat.o fake-rfc2553.o ltc_prng.o ecc.o ecdsa.o crypto_desc.o gensignkey.o gendss.o genrsa.o common-session.o packet.o common-algo.o common-kex.o common-channel.o common-chansession.o termcodes.o loginrec.o tcp-accept.o listener.o process-packet.o common-runopts.o circbuffer.o curve25519-donna.o svr-kex.o svr-auth.o sshpty.o svr-authpasswd.o svr-authpubkey.o svr-authpubkeyoptions.o svr-session.o svr-service.o svr-chansession.o svr-runopts.o svr-agentfwd.o svr-main.o svr-x11fwd.o svr-tcpfwd.o svr-authpam.o libtomcrypt/libtomcrypt.a libtommath/libtommath.a ... (中略) ... arm-linux-androideabi-gcc --sysroot=/usr/local/src/android-4.4_r1/prebuilts/ndk/current/platforms/android-9/arch-arm -o dbclient dbutil.o buffer.o dss.o bignum.o signkey.o rsa.o dbrandom.o queue.o atomicio.o compat.o fake-rfc2553.o ltc_prng.o ecc.o ecdsa.o crypto_desc.o gensignkey.o gendss.o genrsa.o common-session.o packet.o common-algo.o common-kex.o common-channel.o common-chansession.o termcodes.o loginrec.o tcp-accept.o listener.o process-packet.o common-runopts.o circbuffer.o curve25519-donna.o cli-main.o cli-auth.o cli-authpasswd.o cli-kex.o cli-session.o cli-runopts.o cli-chansession.o cli-authpubkey.o cli-tcpfwd.o cli-channel.o cli-authinteract.o cli-agentfwd.o list.o libtomcrypt/libtomcrypt.a libtommath/libtommath.a cli-auth.o:cli-auth.c:function getpass_or_cancel: error: undefined reference to 'getpass' collect2: error: ld returned 1 exit status make: *** [dbclient] Error 1 esaka:dropbear-2013.62 $
また止まってしまった。 けど、 これは dbclient (「db」 といってもデータベースクライアントじゃなくて、 ここでは 「DropBear」 のクライアント) のビルドに失敗しているだけで、 dropbear (ssh サーバ) 自体はビルドできている。 strip して 290 KByte.
esaka:dropbear-2013.62 $ arm-linux-androideabi-strip dropbear esaka:dropbear-2013.62 $ ls -l dropbear -rwxr-xr-x 1 sengoku user 297152 2013-12-11 17:01 dropbear
この dropbear を (adb 経由で) nexus 5 へ送り、 nexus 5 上で実行。 dropbear_dss_host_key および dropbear_rsa_host_key は (サーバであるところの) nexus 5 固有のホスト鍵で、 あらかじめ dropbearkey(8) コマンドを使って作成しておく。
root@hammerhead:/ # /somewhere/dropbear/dropbear \ -r /somewhere/dropbear/dropbear_dss_host_key \ -r /somewhere/dropbear/dropbear_rsa_host_key -F -E [8271] Dec 11 18:18:05 Failed loading /etc/dropbear/dropbear_rsa_host_key [8271] Dec 11 18:18:05 Failed loading /etc/dropbear/dropbear_dss_host_key [8271] Dec 11 18:18:05 Failed loading /etc/dropbear/dropbear_ecdsa_host_key [8271] Dec 11 18:18:05 Not backgrounding
「Failed loading」 すなわちホスト鍵の読み込みに失敗した旨のメッセージが出力されているが、 これはホスト鍵のデフォルトのパスを読みに行ったためで、 「-r」 オプションで指定したホスト鍵の読み込みには成功したので、 ssh サーバとして正常に動作している。
ssh サーバが公開鍵認証を行なうには、 各ユーザのホームディレクトリ下に、 公開鍵を列挙したファイル ~/.ssh/authorized_keys を置く必要がある。 Linux OS など Unix 互換 OS の場合は、 /etc/passwd にユーザID やユーザのホームディレクトリなどを登録する仕組みなので、 ホームディレクトリの場所は自由に設定できるが、 android OS の場合は、 ユーザを自由に登録できるようにはなっていなくて、 ユーザID はソース $TOP/system/core/include/private/android_filesystem_config.h で定義 (ハードコーディング) されている。 しかも、 uid が 10000未満の (システム) ユーザは、 ホームディレクトリが 「/」(ルート) に、 uid が 10000以上の (アプリ) ユーザは 「/data」 に、 それぞれ固定されている (ソース $TOP/bionic/libc/bionic/stubs.cpp 参照)。
したがって、 dropbear のソースに (さらに) 手を加えてホームディレクトリ以外に公開鍵を登録できるようにするか、 あるいは ルート直下 /.ssh/authorized_keys に公開鍵を登録するか、 いずれかの方法を選ぶことになる。 dropbear に手を加えすぎると、 dropbear がバージョンアップしたとき (セキュリティホールが見つかって修正が必要になったときなど) に追随するのが面倒になるので、 私は後者の方法を選んだ。 android OS の場合、 ルート直下は RAM ディスク (initramfs) なので電源を切ると揮発してしまう。 そこで、 android OS の起動時に以下のスクリプトを走らせて /.ssh/authorized_keys に公開鍵をコピーするようにした。
if [ -d /data/cust/ROOT ]; then mount -oremount,rw null / cp -a /data/cust/ROOT/.ssh / mount -oremount,ro null / dropbear=/data/cust/dropbear $dropbear/dropbear -r $dropbear/dropbear_dss_host_key \ -r $dropbear/dropbear_rsa_host_key fi
nexus 5 の場合、 起動時に /system/etc/install-recovery.sh が実行されるので、 このファイルの末尾にでも上記スクリプトを書き加えておけばよい。 これで root の他、 ホームディレクトリ 「/」 を共有している uid が 10000未満のユーザアカウント (system, wifi, adb, shell など) にログインできる。
以上で nexus 5 へ ssh ログインするための手はずが整ったので、 PC からログインしてみる:
esaka:~ $ ssh -l root 192.168.18.147 PTY allocation request failed on channel 0 shell request failed on channel 0 esaka:~ $ ssh -l root 192.168.18.147 sh -i sh: can't find tty fd: No such device or address sh: warning: won't have full job control root@hammerhead:/ #
ssh の引数に 「sh -i」 を付けて実行しているのは、 今回ビルドした dropbear では PTY (Pseudo TTY, 仮想端末) が利用できないため (「PTY allocation request failed」)。 元々 dropbear は openpty(3) を使って PTY をオープンするが、 android の libc には openpty(3) が存在しない (上記 ./configure の出力を参照)。 openpty(3) 自体は簡単に実装できるので、 dropbear が PTY を利用できるように修正することは容易だが、 あまり dropbear のソースに手を加えたくないため、 とりあえず PTY が使えないままビルドした。
「192.168.18.147」 は、 私の自宅の無線LAN セグメントにおいて、 nexus 5 に割当てられた IP アドレス。 PTY は利用できないが nexus 5 に無事ログインできた。 サーバー側から (もとい、nexus 5 側から) 見ると、 こんな感じ:
[8298] Dec 11 18:18:17 Child connection from ::ffff:192.168.18.20:48647 void setusershell()(3) is not implemented on Android void endusershell()(3) is not implemented on Android void setusershell()(3) is not implemented on Android void endusershell()(3) is not implemented on Android void setusershell()(3) is not implemented on Android void endusershell()(3) is not implemented on Android [8298] Dec 11 18:18:17 Pubkey auth succeeded for 'root' with key md5 56:c3:d5:dc:83:f4:0c:df:fc:4e:42:36:6a:dd:ff:c1 from ::ffff:192.168.18.20:48647
余計な警告 (「not implemented on Android」) が出ているが、 ちゃんと公開鍵認証できている。 一般に公開鍵認証の処理は CPU 負荷がかかるが、 ほとんどストレス無くログインできてしまうあたり、 下手なサーバより速いんじゃなかろうか > nexus 5
次に rsync をインストールする。
rsync 公式サイトから最新版 3.1.0 をダウンロードしてきてビルド。 rsync の configure は 「--host=arm-linux-androideabi」 オプションを指定できないので、 「CC=」 でクロスコンパイラを指定する。
esaka:rsync-3.1.0 $ TOP=/usr/local/src/android-4.4_r1 esaka:rsync-3.1.0 $ SYSROOT=$TOP/prebuilts/ndk/current/platforms/android-9/arch-arm esaka:rsync-3.1.0 $ export CFLAGS=--sysroot=$SYSROOT esaka:rsync-3.1.0 $ export LDFLAGS=--sysroot=$SYSROOT esaka:rsync-3.1.0 $ CC=$TOP/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/arm-linux-androideabi-gcc ./configure --host=arm-linux-gnueabi --disable-largefile configure.sh: WARNING: if you wanted to set the --build type, don't use --host. If a cross compiler is detected then cross compile mode will be used configure.sh: Configuring rsync 3.1.0 checking build system type... x86_64-unknown-linux-gnu checking host system type... arm-unknown-linux-gnueabi ... (中略) ... configure.sh: creating ./config.status config.status: creating Makefile config.status: creating lib/dummy config.status: creating zlib/dummy config.status: creating popt/dummy config.status: creating shconfig config.status: creating config.h config.status: config.h is unchanged rsync 3.1.0 configuration successful esaka:rsync-3.1.0 $ make perl ./mkproto.pl ./*.c ./lib/compat.c /usr/local/src/android-4.4_r1/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/arm-linux-androideabi-gcc -std=gnu99 -I. -I. --sysroot=/usr/local/src/android-4.4_r1/prebuilts/ndk/current/platforms/android-9/arch-arm -DHAVE_CONFIG_H -Wall -W -I./popt -I./zlib -Wno-unused-parameter -c flist.c -o flist.o ... (中略) ... /usr/local/src/android-4.4_r1/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/arm-linux-androideabi-gcc -std=gnu99 --sysroot=/usr/local/src/android-4.4_r1/prebuilts/ndk/current/platforms/android-9/arch-arm -DHAVE_CONFIG_H -Wall -W -I./popt -I./zlib -Wno-unused-parameter --sysroot=/usr/local/src/android-4.4_r1/prebuilts/ndk/current/platforms/android-9/arch-arm -o rsync flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o util.o util2.o main.o checksum.o match.o syscall.o log.o backup.o delete.o options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o progress.o pipe.o params.o loadparm.o clientserver.o access.o connection.o authenticate.o lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattrs.o lib/getpass.o zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o popt/findme.o popt/popt.o popt/poptconfig.o popt/popthelp.o popt/poptparse.o sed 's;\@bindir\@;/usr/local/bin;g' <./rsync-ssl.in >rsync-ssl sed 's;\@stunnel4\@;stunnel;g' <./stunnel-rsync.in >stunnel-rsync sed 's;\@bindir\@;/usr/local/bin;g' <./stunnel-rsyncd.conf.in >stunnel-rsyncd.conf esaka:rsync-3.1.0 $ ls -l rsync -rwxr-xr-x 1 sengoku user 758924 2013-12-11 22:14 rsync esaka:rsync-3.1.0 $ file rsync rsync: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped esaka:rsync-3.1.0 $ $TOP/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/arm-linux-androideabi-strip rsync esaka:rsync-3.1.0 $ ls -l rsync -rwxr-xr-x 1 sengoku user 642140 2013-12-11 22:14 rsync esaka:rsync-3.1.0 $
特に問題もなく、 あっさりビルドできてしまった。 rsync 3.0.9 までは android 用をビルドするには一部修正が必要だったが、 3.1.0 は無修整でビルドできるようだ。 できた rsync を nexus 5 の /system/bin へコピー。
以上で、 多数のサーバを一括管理するがごとく、 多数のスマホを一括管理することが可能になった。 これで、 スマホごとに内容がちょっとずつ違う、などといった事態を避けることができる。 もちろん、 google 等の 「クラウド同期」 で用が足りることも多いが、 本当に重要なデータは、 クラウドなんかに任せたくはないですよね?