ネットワーク接続は"IPアドレス"と"ポート番号"のペア
からなっています。ネットワークプログラミングのAPI(Applications Program
Interface)はsockets apiと呼ばれます。ソケットは開いたファイルのように
振舞い、これに対する読み書きをすることでネットワーク経由でデータをやりとり
ができます。
ローカルソケットのIPアドレスを返すファンクションコールgetsockname
があります。
virtualdはgetsockname(訳注:/lib/libc.so)を、
どのローカルマシンのIPがアクセスされているかを決めるのに用います。
Virtualdは設定ファイルを読み込み、そのIPに対応するディレクトリを取得します。
そのディレクトリに chrootした後、実際に行われるサービスに接続を
引き渡します。
chroot はルートディレクトリ'/' を別のディレクトリにセット
し直し、そのディレクトリ(新しいルートディレクトリ)よりも上にある全てのもの
は実行されているプログラムからは見えません(切り離されます)。
こうして各IPアドレスはそれぞれの仮想ファイルシステムを取得します。
これはネットワークプログラムからは透過的なので(上に書いたような操作は
隠してあるので)、プログラムは何事もなかったかのように動作します。
このようにして、inetdといったプログラムと連結されたVirtuald はいろいろなサービスを仮想化して使うことができるのです。
Inetdは複数のポートを監視し、接続があった場合に(例えばpop要求があったとき などに)ネットワークネゴシエーション行って指定されたプログラムに接続を 引き渡すスーパーサーバです。 これにより、必要がなくて何もしていないサーバがないようにします。
標準的な /etc/inetd.confファイルは
ftp stream tcp nowait root /usr/sbin/tcpd wu.ftpd -l -a pop-3 stream tcp nowait root /usr/sbin/tcpd in.qpop -sとなります。(訳注:pop-3はpop3の場合はpop3にして下さい)。
また仮想的な/etc/inetd.confファイルは
ftp stream tcp nowait root /usr/bin/virtuald virtuald /virtual/conf.ftp wu.ftpd -l -a pop-3 stream tcp nowait root /usr/bin/virtuald virtuald /virtual/conf.pop in.qpop -sとなります。
それぞれのサービスに対応するIPとディレクトリをコントロールする confファイルを取得します。一つのマスターconfファイルがあり、また ドメインの異なるリストのサービスが必要なときはそれに応じたconfファイル を用意することができます。virtual.confは以下のような内容です。
# This is a comment and so are blank lines # Format IP <SPACE> dir <NOSPACES> 10.10.10.129 /virtual/foo.bar.com 10.10.10.130 /virtual/bar.foo.com 10.10.10.157 /virtual/boo.la.com(訳注:実際の中身は各設定環境に合わせて書き換えて下さい。 そしてこのファイルは(上のinitd.confの設定に合わせた)該当ディレクトリ およびファイル名にコピーします)。
(訳注:このVirtualdのソースは gcc -o viturald virtuald.cとして
コンパイルできます。また後の文章と合わせるために/usr/binに
コピーします)
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <stdio.h>
#define BUFSIZE 8192
main(int argc,char **argv)
{
char buffer[BUFSIZE];
char *ipaddr,*dir;
logit("Virtuald Starting: $Revision: 1.1 $");
if (!argv[1])
{
logit("invalid arguments: no conf file");
quitting_virtuald(0);
}
if (!argv[2])
{
logit("invalid arguments: no program to run");
quitting_virtuald(0);
}
if (getipaddr(&ipaddr))
{
logit("getipaddr failed");
quitting_virtuald(0);
}
sprintf(buffer,"Incoming ip: %s",ipaddr);
logit(buffer);
if (iptodir(&dir,ipaddr,argv[1]))
{
logit("iptodir failed");
quitting_virtuald(0);
}
if (chroot(dir)<0)
{
logit("chroot failed: %m");
quitting_virtuald(0);
}
sprintf(buffer,"Chroot dir: %s",dir);
logit(buffer);
if (chdir("/")<0)
{
logit("chdir failed: %m");
quitting_virtuald(0);
}
if (execvp(argv[2],argv+2)<0)
{
logit("execvp failed: %m");
quitting_virtuald(0);
}
}
int logit(char *buf)
{
openlog("virtuald",LOG_PID,LOG_DAEMON);
syslog(LOG_ERR,buf);
closelog();
return 0;
}
int quitting_virtuald(int retval)
{
exit(retval);
return 0;
}
int getipaddr(char **ipaddr)
{
struct sockaddr_in virtual_addr;
static char ipaddrbuf[BUFSIZE];
int virtual_len;
char *ipptr;
virtual_len=sizeof(virtual_addr);
if (getsockname(0,(struct sockaddr *)&virtual_addr,&virtual_len)<0)
{
logit("getipaddr: getsockname failed: %m");
return -1;
}
if (!(ipptr=inet_ntoa(virtual_addr.sin_addr)))
{
logit("getipaddr: inet_ntoa failed: %m");
return -1;
}
strncpy(ipaddrbuf,ipptr,sizeof(ipaddrbuf)-1);
*ipaddr=ipaddrbuf;
return 0;
}
int iptodir(char **dir,char *ipaddr,char *filename)
{
char buffer[BUFSIZE],*bufptr;
static char dirbuf[BUFSIZE];
FILE *fp;
if (!(fp=fopen(filename,"r")))
{
logit("iptodir: fopen failed: %m");
return -1;
}
*dir=NULL;
while(fgets(buffer,BUFSIZE,fp))
{
buffer[strlen(buffer)-1]=0;
if (*buffer=='#' || *buffer==0)
continue;
if (!(bufptr=strchr(buffer,' ')))
{
logit("iptodir: strchr failed");
return -1;
}
*bufptr++=0;
if (!strcmp(buffer,ipaddr))
{
strncpy(dirbuf,bufptr,sizeof(dirbuf)-1);
*dir=dirbuf;
break;
}
}
if (fclose(fp)==EOF)
{
logit("iptodir: fclose failed: %m");
return -1;
}
if (!*dir)
{
logit("iptodir: ip not found in conf file");
return -1;
}
return 0;
}