Ogni connessione di rete è basata su due coppie di indirizzi IP e
porte. L'`API' (Applications Program Interface) per la programmazione di
rete viene chiamata `Socket API'. Un `socket' si comporta come un file
aperto, con operazioni di lettura/scrittura su di esso è possibile
scambiare dati su una connessione di rete. C'è una funzione chiamata
getsockname
che restituisce l'indirizzo IP del socket locale.
Virtuald in primo luogo utilizza getsockname
per determinare a
quale indirizzo IP della macchina locale si vuole accedere. Quindi legge da
un file di configurazione quale directory è associata a tale
indirizzo IP. Virtuald fa chroot
a quella directory e passa la
connessione al servizio. Chroot
reimposta `/', la directory radice,
in un nuovo punto dell'albero delle directory, in modo tale che il programma
in esecuzione non possa accedere a nulla fuori da questo ramo.
Quindi ogni indirizzo IP è associato ad un proprio filesystem
virtuale. Tutto ciò è trasparente per il programma di rete,
che si comporterà come se niente fosse successo. Virtuald può
quindi essere utilizzato insieme con un programma come inetd per rendere
virtuale un servizio.
Inetd è un super server di rete che sta in ascolto su varie porte e, quando riceve una connessione (ad esempio, una richiesta pop in entrata), effettua la fase di negoziazione e passa la connessione ad un programma che gestisce lo specifico servizio. Questo per evitare che vengano eseguiti dei servizi che restano inattivi quando inutilizzati.
Un file /etc/inetd.conf standard appare così:
ftp stream tcp nowait root /usr/sbin/tcpd \ wu.ftpd -l -a pop-3 stream tcp nowait root /usr/sbin/tcpd \ in.qpop -s
Il file /etc/inetd.conf di un sistema in cui si utilizza virtuald appare così:
ftp stream tcp nowait root /usr/local/bin/virtuald \ virtuald /virtual/conf.ftp wu.ftpd -l -a pop-3 stream tcp nowait root /usr/local/bin/virtuald \ virtuald /virtual/conf.pop in.qpop -s
Ciascun servizio ha un file di configurazione che controlla quali indirizzi IP e directory sono autorizzati per quel servizio. Si può avere o un unico file principale oppure più file di configurazione, se si desidera che ad ogni servizio sia associato una lista diversa di domini. Un tipico file di configurazione appare così:
# Questo è un commento e allo stesso modo vengono trattate le linee vuote # Formato: IndirizzoIP [spazio] directory [nessun spazio] 10.10.10.129 /virtual/domain1.com 10.10.10.130 /virtual/domain2.com 10.10.10.157 /virtual/domain3.com # Opzione predefinita per tutti gli altri indirizzi IP default /
Questo è il codice sorgente in C del programma virtuald. È necessario compilarlo e installarlo in /usr/local/bin con permessi 0755, utente root, e gruppo root. L'unica opzione di compilazione è VERBOSELOG che attiva/disattiva la registrazione delle connessioni nei file di log:
#include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdarg.h> #include <unistd.h> #include <string.h> #include <syslog.h> #include <stdio.h> #undef VERBOSELOG #define BUFSIZE 8192 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) { syslog(LOG_ERR,"getipaddr: getsockname failed: %m"); return -1; } if (!(ipptr=inet_ntoa(virtual_addr.sin_addr))) { syslog(LOG_ERR,"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"))) { syslog(LOG_ERR,"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,' '))) { syslog(LOG_ERR,"iptodir: strchr failed"); return -1; } *bufptr++=0; if (!strcmp(buffer,ipaddr)) { strncpy(dirbuf,bufptr,sizeof(dirbuf)-1); *dir=dirbuf; break; } if (!strcmp(buffer,"default")) { strncpy(dirbuf,bufptr,sizeof(dirbuf)-1); *dir=dirbuf; break; } } if (fclose(fp)==EOF) { syslog(LOG_ERR,"iptodir: fclose failed: %m"); return -1; } if (!*dir) { syslog(LOG_ERR,"iptodir: ip not found in conf file"); return -1; } return 0; } int main(int argc,char **argv) { char *ipaddr,*dir; openlog("virtuald",LOG_PID,LOG_DAEMON); #ifdef VERBOSELOG syslog(LOG_ERR,"Virtuald Starting: $Revision: 1.49 $"); #endif if (!argv[1]) { syslog(LOG_ERR,"invalid arguments: no conf file"); exit(0); } if (!argv[2]) { syslog(LOG_ERR,"invalid arguments: no program to run"); exit(0); } if (getipaddr(&ipaddr)) { syslog(LOG_ERR,"getipaddr failed"); exit(0); } #ifdef VERBOSELOG syslog(LOG_ERR,"Incoming ip: %s",ipaddr); #endif if (iptodir(&dir,ipaddr,argv[1])) { syslog(LOG_ERR,"iptodir failed"); exit(0); } if (chroot(dir)<0) { syslog(LOG_ERR,"chroot failed: %m"); exit(0); } #ifdef VERBOSELOG syslog(LOG_ERR,"Chroot dir: %s",dir); #endif if (chdir("/")<0) { syslog(LOG_ERR,"chdir failed: %m"); exit(0); } if (execvp(argv[2],argv+2)<0) { syslog(LOG_ERR,"execvp failed: %m"); exit(0); } closelog(); exit(0); }