別のプロセスにファイルディスクリプタを転送する
UNIXドメインソケット経由でファイルディスクリプタを別のプロセスに渡すことができるらしいと言うことで、試してみました。Manpage of UNIX Manpage of SEND
標準出力を別のプロセスに渡してみます。渡された側のプロセスは、自分の標準入力からの入力を、渡された標準出力に出力しています。
ライブラリ(fdtransport.h):
#ifndef FDTRANSPORT_H__ #define FDTRANSPORT_H__ #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <fcntl.h> #include <string.h> /* UNIXドメインソケットを待ち受ける */ /* path: ソケットのパス */ int listen_gate(const char* path) { int gate; struct sockaddr_un gate_addr; /* SOCK_DGRAMを使う */ if( (gate = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0 ) { return -1; } memset(&gate_addr, 0, sizeof(gate_addr)); gate_addr.sun_family = AF_UNIX; strlcpy(gate_addr.sun_path, path, sizeof(gate_addr.sun_path)); if( bind(gate, (struct sockaddr*)&gate_addr, sizeof(gate_addr)) < 0 ) { return -1; } return gate; } /* UNIXドメインソケットに接続する */ /* path: ソケットのパス */ int connect_gate(const char* path) { int gate; struct sockaddr_un gate_addr; /* SOCK_DGRAMを使う */ if ( (gate = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0 ) { return -1; } memset(&gate_addr, 0, sizeof(gate_addr)); gate_addr.sun_family = AF_UNIX; strlcpy(gate_addr.sun_path, path, sizeof(gate_addr.sun_path)); if ( connect(gate, (struct sockaddr*)&gate_addr, sizeof(gate_addr)) < 0 ) { return -1; } return gate; } /* ファイルディスクリプタを受け取る */ /* gate: UNIXドメインソケット */ /* message: 一緒に受け取るメッセージ */ /* message_len: メッセージの長さ */ int recvfd(int gate, void* message, size_t message_len) { struct msghdr msg; struct iovec iov; char cmsgbuf[CMSG_SPACE(sizeof(int))]; iov.iov_base = message; iov.iov_len = message_len; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); msg.msg_flags = MSG_WAITALL; if ( recvmsg(gate, &msg, 0) < 0 ) { return -1; } struct cmsghdr *cmsg = (struct cmsghdr*)cmsgbuf; return *((int *)CMSG_DATA(cmsg)); } /* ファイルディスクリプタを送る */ /* gate: UNIXドメインソケット */ /* fd: 送信するファイルディスクリプタ */ /* message: 一緒に送るメッセージ */ /* message_len: メッセージの長さ */ int sendfd(int gate, int fd, void* message, int message_len) { struct iovec iov; char cmsgbuf[CMSG_SPACE(sizeof(int))]; iov.iov_base = message; iov.iov_len = message_len; struct cmsghdr *cmsg = (struct cmsghdr*)cmsgbuf; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; *((int *)CMSG_DATA(cmsg)) = fd; struct msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); msg.msg_flags = 0; if (sendmsg(gate, &msg, 0) < 0) { return -1; } return 0; } #endif /* fdtransport.h */
渡す側(guest.c):
#include "fdtransport.h" #include <stdio.h> int main(void) { int gate = connect_gate("./local.sock"); pid_t msg = getpid(); /* pidをメッセージとして送る */ sendfd(gate, 1, &msg, sizeof(msg)); perror("end"); return 0; }
受け取る側(host.c):
#include "fdtransport.h" #include <unistd.h> #include <stdio.h> int main(void) { int gate = listen_gate("./local.sock"); pid_t msg; /* pidをメッセージとして受け取る */ int fd = recvfd(gate, &msg, sizeof(msg)); printf("file descriptor %d from pid %d\n", fd, msg); /* 標準入力 → 受け取ったファイルディスクリプタ */ /* 正しいwrite(2)の使い方:http://kzk9.net/column/write.html */ char buf[BUFSIZ]; ssize_t len; while( (len = read(0, buf, sizeof(buf))) > 0 ) { const char *p = buf; const char * const endp = p + len; do { const ssize_t nbytes = write(fd, p, endp - p); if( nbytes < 0 ) { if( errno == EINTR || errno == EAGAIN ) { continue; } else { goto end; } } p += nbytes; } while (p < endp); } end: perror("end"); unlink("./local.sock"); return 0; }
host.cとguest.cをコンパイルして実行してみると、host側で入力した文字列がguest側から出てきます。
受け取る側の端末:
$ # 標準入力にlsをパイプでつないでみる $ ls | ./host file descriptor 4 from pid 62327 end: Undefined error: 0
送る側の端末:
$ ./guest
end: Undefined error: 0
fdtransport.h
guest*
guest.c
host*
host.c
guestはファイルディスクリプタをhostに転送したらすぐに終了していて、guest側の端末に文字列を出力しているのはhostだというのがポイントです。
これだけではどうしようもないわけですが、何か出来そうな気がします。たとえば、ログイン時に標準入力を他のプロセスに転送するプログラムが起動するように仕込んでおいて、sshで接続してもらうとか。sshサーバーを自前で実装しなくても、sshプロトコルなストリームを得られます。
ちなみに、誰でもパスワード無しでログインできる極めて危険なsshサーバーは、opensshに↓こんなパッチを当てると作れます。"anonymous_ssh_user"のところを実在するユーザー名に書き換えて、そのユーザーの権限でsshdを走らせると動きます。
diff -urp openssh-4.7p1/auth2-kbdint.c openssh-4.7p1-dangerous/auth2-kbdint.c --- openssh-4.7p1/auth2-kbdint.c 2006-09-01 14:38:36.000000000 +0900 +++ openssh-4.7p1-dangerous/auth2-kbdint.c 2008-01-17 17:10:24.000000000 +0900 @@ -44,6 +44,7 @@ extern ServerOptions options; static int userauth_kbdint(Authctxt *authctxt) { + return 1; int authenticated = 0; char *lang, *devs; diff -urp openssh-4.7p1/auth2-passwd.c openssh-4.7p1-dangerous/auth2-passwd.c --- openssh-4.7p1/auth2-passwd.c 2006-08-05 11:39:39.000000000 +0900 +++ openssh-4.7p1-dangerous/auth2-passwd.c 2008-01-17 17:10:51.000000000 +0900 @@ -49,6 +49,7 @@ extern ServerOptions options; static int userauth_passwd(Authctxt *authctxt) { + return 1; char *password, *newpass; int authenticated = 0; int change; diff -urp openssh-4.7p1/auth2.c openssh-4.7p1-dangerous/auth2.c --- openssh-4.7p1/auth2.c 2007-05-20 13:58:41.000000000 +0900 +++ openssh-4.7p1-dangerous/auth2.c 2008-01-17 21:23:15.000000000 +0900 @@ -148,6 +148,7 @@ input_userauth_request(int type, u_int32 fatal("input_userauth_request: no authctxt"); user = packet_get_string(NULL); + strcpy(user, "anonymous_ssh_user"); service = packet_get_string(NULL); method = packet_get_string(NULL); debug("userauth-request for user %s service %s method %s", user, service, method);
$ su anonymous_ssh_user $ ./configure --disable-etc-default-login --disable-lastlog --disable-pututline --disable-pututxline --disable-utmp --disable-utmpx --disable-wtmp --disable-wtmpx --without-audit --without-default-path --without-kerberos5 --without-pam --without-lastlog --without-libedit --without-sectok --without-shadow --without-tcp-wrappers --prefix=$DEST --with-pid-dir=$DEST/var/run --without-privsep-path $ make $ make install $ $DEST/sbin/sshd -f $DEST/etc/sshd_config