Lo strato applicativo di Internet
Utilizzeremo il server HTTP Apache, il più diffuso in assoluto.
Si narra che il nome Apache derivi da una contrazione di a patchy server, dato che inizialmente, non era costituito da nient'altro che una serie di patch (pezze) al preesistente server di NCSA.
Il suo funzionamento prevede il pre-fork di alcuni processi figli, in modo che le nuove richieste HTTP non vengano rallentate nell'attesa della creazione di un nuovo processo figlio. Oppure, si può ricorrere ad un modulo orientato al multi-threading come worker.
In ogni modo, l'architettura di Apache prevede di delegare molti tipi di elaborazioni possibili ad un insieme di diversi moduli che implementano ognuno una diversa funzionalità. In questo modo, si rende indipendente lo sviluppo dei moduli, e delle funzionalità associate, da quello del nucleo del server web.
Con Synaptic, scarichiamo ed installiamo apache2, apache2-doc e apache2-utils. Impartendo il comando ps axf | grep apache, verifichiamo se è già in esecuzione, ed in caso contrario, lanciamo il server web con il comando sudo service apache2 start. Apriamo il Browser web, e visitiamo la URI http://127.0.0.1. Apparirà una pagina, con una sola frase: It works! a conferma del successo dell'operazione.
Apache basa la sua configurazione su di un nutrito insieme di files, che troviamo a partire dalla directory /etc/apache2, e di cui il principale è (/etc/apache2/apache2.conf) che, oltre ad impostare alcuni parametri di base, provvede a referenziare gli altri files di configurazione, ognuno dedicato ad un aspetto particolare.
Include /etc/apache2/mods-enabled/ *.load Include /etc/apache2/mods-enabled/ *.conf |
che attivano il caricamento (.load) e la configurazione (.conf) dei moduli associati a tutti i files presenti nella directory mods-enabled. Se investighiamo sul contenuto della directory /etc/apache2/mods-enabled però, vi troviamo una serie di collegamenti simbolici a files dallo stesso nome, ma che si trovano in /etc/apache2/mods-available. Per abilitare un modulo quindi, occorre creare il link simbolico per i suoi due files di configurazione, dalla seconda directory alla prima. Per semplificare questa operazione, sono disponibili i comandi a2enmod e a2dismod, rispettivamente per abilitare e disabilitare un modulo, come ad esempio
sudo a2dismod info # disabilita il modulo info sudo a2enmod info # ri-abilita il modulo info |
Lo stesso server Apache può ospitare diversi siti virtuali, per ognuno dei quali è possibile avere un diverso file di configurazione, presente presso la directory /etc/apache2/sites-available. D'altra parte, in fondo al file di configurazione principale /etc/apache2/apache2.conf troviamo la direttiva Include /etc/apache2/sites-enabled/ che provvede ad attivare solo i siti definiti dai files presenti in questa seconda directory. In modo del tutto analogo ai moduli, i diversi siti vengono vitalizzati creando un link simbolico all'interno della directory /etc/apache2/sites-enabled verso i loro file di configurazione, mediante il comando sudo a2ensite info. A seguito della installazione, risulta già pre-impostato un unico sito principale.
Come già fatto in una precedente esercitazione, pubblichiamo le nostre pagine sotto la propria directory di utente. Prima di tutto, dobbiamo abilitare il modulo userdir, che consente appunto l'uso delle directory di utente. Per questo, eseguiamo i comandi
sudo a2enmod userdir sudo service apache2 force-reload |
Creiamo quindi la directory public_html in /home/labint, scriviamo al suo interno un file di test (es prova.txt) contenente una frase simpatica, e proviamo a visualizzare il file, referenziando la URI http://127.0.0.1/~labint/prova.txt. Come dite? Compare un messaggio Forbidden ??
Per verificare l'esito delle operazioni in corso, possiamo andare a leggere le informazioni salvate nei file di log, ossia /var/log/apache2/access.log che documenta gli accessi, e /var/log/apache2/error.log, che documenta le circostanze di errore. Quest'ultimo contiene l'informazione che cercavamo, ossia il messaggio
[Wed May 30 08:32:02 2007] [error] [client 127.0.0.1] (13): file permissions deny server access: /home/labint/public_html/prova.txt, referer: http://127.0.0.1/~labint/ |
Le informazioni che compaiono nei file di log, come access.log, possono essere personalizzate mediante le direttive LogFormat and CustomLog anche allo scopo di particolarizzare il tipo di report generato dagli appositi programmi di analisi giornaliera.
Ma, oltre alle directory degli utenti, come si fa ad usare direttamente il FQDN del sito, con una URL del tipo http://localhost/pagina.html ? Le definizioni legate a questa possibilità, si trovano nel file /etc/apache2/sites-enabled/000-default, in cui si dichiara, tra le altre cose, che la DocumentRoot del web server corrisponde alla directory /var/www/, e quindi è lì che dobbiamo salvare i files corrispondenti a path della URI immediatamente successivi al nome a dominio del server.
Abbiamo parlato di sito principale, perché presso lo stesso computer possono essere ospitati siti diversi, corrispondenti a nomi a dominio diversi, ma relativi (mediante dei RR del DNS di tipo CNAME) allo stesso indirizzo IP. Questa tecnica prende il nome di Virtual Host, e si basa sul fatto che il nome a dominio che compare nella URL di richiesta, è comunque indicato nell'header Host della stessa. In virtù di questo, Apache si basa sulle impostazioni contenute nei files di configurazione presenti in /etc/apache2/sites-enabled/, per individuare la configurazione del Virtual Host corrispondente, e mappare la richiesta su diverse parti del filesytem.
Il package di documentazione che abbiamo installato copia nelle opportune directory dello stesso server web le pagine accessibii a partire da http://127.0.0.1/manual/, che sono copie conformi di quanto compare presso il sito della fondazione Apache. Per apprezzare le direttive di configurazione che consentono la comparsa della nostra pagina on-line, possiamo leggere l'How-to corrispondente a Per-user Web Directories (public_html), che ci guida alla configurazione di Apache indicando una procedura operativa, ed al contempo fornendo i links alla documentazione delle direttive usate.
Il primo impatto con le direttive, i moduli, ed contesti, che definiscono il comportamento di apache, può scoraggiare chi è abituato alle interfacce di configurazione grafica: d'altra parte, affrontando un problema alla volta, dopo qualche tempo si apprezza l'elevato grado di configurabilità ottenibile. Dei moduli abbiamo già parlato; per quanto riguarda le direttive (ad es userdir), la documentazione relativa utilizza una terminologia il cui significato è documentato a parte. In particolare, le direttive sono attivabili all'interno di specifici contesti, come ad esempio <Directory> o <Location>, che individuano il loro campo di applicabilità. Per esempio, in userdir.conf troviamo
UserDir public_html # la dir di utente da
rendere accessibile UserDir disabled root # per l'utente root non è accessibile <Directory /home/*/public_html> AllowOverride FileInfo AuthConfig Limit Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec </Directory> |
che indica come, a seguito della attivazione del modulo userdir, le direttive AllowOverride etc etc, si applichino a tutte le directory di utente /home/*/public_html. Il meccanismo dei contesti permette di limitare l'effetto delle direttive di configurazione, solo a particolari sezioni del sito.
Ma oltre ai files presenti in /etc/apache2/mods-enabled, la configurazione di apache può essere ulteriormente modificata anche mediante dei file .htaccess, dislocati nelle stesse directory in cui si trovano i documenti da servire, in modo da permettere agli utenti stessi di impostare le loro preferenze, per i contesti in cui si trovano i loro contenuti.
Sicuramente un indirizzo in cui compare una tilde (~) non è dei più belli da dare in giro, e per costruire un indirizzo più leggibile possiamo ricorrere ad una tra due direttive che ci vengono in soccorso: Alias e Redirect, in cui la prima serve per mettere in corrispondenza la parte path della URI, con un percorso nel filesystem locale, mentre la seconda serve per inviare risposte di tipo 30x Redirect, anche verso siti esterni, in corrispondenza di URI particolari. Mentre l'argomento nel suo complesso è ben trattato nella documentazione, indichiamo qui la direttiva da inserire nel file nostro alias.conf (sudo gedit /etc/apache2/mods-enabled/alias.conf), per far corrispondere all'indirizzo http://127.0.0.1/~labint, un più leggibile http://127.0.0.1/labint:
Alias /labint "/home/labint/public_html" |
e quindi, far rileggere la configurazione ad apache con il comando sudo service apache2 reload.
Anche se la nostra prima pagina non è un granché, possiamo comunque proteggerla da sguardi indiscreti. Nella documentazione di Apache troviamo le indicazioni su come procedere, e seguendo quei consigli, possiamo fare così:
htpasswd -c /home/labint/passwords labint |
AuthType Basic AuthName "Laboratorio Internet" AuthUserFile /home/labint/passwords Require user labint |
Notiamo che non è stato necessario ri-lanciare il server apache perché le modifiche avessero effetto: questo vuol dire che i file .htaccess vengono ri-letti ad ogni nuova richiesta, e questo può essere un elemento di inefficienza all'aumentare del carico, per cui se si ha la possibilità di intervenire sui files di configurazione di sistema, è preferible modificare quelli.
Nel caso si scelga questo tipo più sicuro di autenticazione, la versione delle password residente presso il server dovà essere generata mediante l'utility htdigest anziché htpasswd, dato il diverso meccanismo utilizzato, ed usata anche la direttiva AuthDigestDomain.
Nell'esempio svolto, le credenziali degli utenti presso il server sono memorizzate su di un semplice file piatto. All'aumentare delle dimensioni di questo, e della sua frequenza di utilizzo, questo metodo può divenire inefficiente. Esiste allora la possibilità di accedere alle credenziali da verificare mediante un file indicizzato (modulo authn_dbm), un DBMS (modulo authn_dbd), od un server LDAP (modulo authnz_ldap).
Può succedere che si voglia interdire l'accesso ad una pagina, od una directory, nei riguardi delle richieste che giungono da un certo computer, o da un gruppo di computer. Le regole generali prevedono l'uso delle direttive Allow, Deny e Order, ma per una sperimentazione semplice semplice, ci si può limitare ad inserire nel file /home/labint/public_html/.htaccess la direttiva
Deny from All |
e quindi, provando ad accedere di nuovo alla nostra pagina, possiamo verificare come ora l'accesso sia divenuto proibito. Una volta fatta la prova, commentiamo la direttiva, anteponendo un #.
Nel file /etc/apache2/mods-enabled/mime.conf, compare la direttiva
TypesConfig /etc/mime.types |
AddType MIME-type extension [extension] |
Seguendo (più o meno) i suggerimenti riportati nell'How-To fornito con Apache, per sperimentare l'esecuzione di codice da parte di un server web possiamo modificare il file di configurazione relativo alle directory degli utenti (sudo gedit /etc/apache2/mods-enabled/userdir.conf) in modo che appaia come segue:
<IfModule mod_userdir.c> UserDir public_html UserDir disabled root <Directory /home/*/public_html> AllowOverride FileInfo AuthConfig Limit Indexes Options ExecCGI MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec AddHandler cgi-script .cgi </Directory> </IfModule> |
In particolare, la direttiva Options ExecCGI è quella che abilita l'esecuzione di codice CGI, mentre AddHandler istruisce Apache a trattare i files con estensione .cgi come programmi eseguibili, eseguirli, e tentare di inviare lo standard output risultante al browser che aveva referenziato tale programma. Quindi, non tutti i programmi presenti in /home/labint/public_html saranno eseguiti, ma solo quelli con estensione .cgi. Se nella propria versione del file ci sono cose in più, non importa; verifichiamo invece che non ce ne siano in meno e, se abbiamo apportato modifiche, chiediamo ad Apache di rileggere la configurazione con il comando sudo service apache2 reload
Creiamo ora il nostro CGI (gedit primocgi.cgi) utilizzando il linguaggio Perl, in modo che appaia come segue:
#!/usr/bin/perl # # - primocgi - visualizzazione delle variabili di ambiente ricevute # print "Content-type: text/plain\n\n"; print "Salute a tutti.\n\n"; print "State osservando lo standard output prodotto da $0.\n"; print "I files presenti in questa directory sono:\n\n"; system "ls -l"; print "\n o o o o o o o o o o o o o o o o o o o o \n\n"; print "Qui sotto, mostriamo i valori associati alle variabili di ambiente che apache rende accessibili a questo script:\n\n"; foreach $key (keys %ENV) { # scandisce la lista delle chiavi di %ENV print "$key --> $ENV{$key}\n"; # mostra le coppie chiave-valore } |
e quindi, ricordiamoci di rendere eseguibile il programma, anche da parte di Apache: chmod 755 primocgi.cgi. In alternativa, possiamo direttamente installare presso /home/labint/public_html questo e gli altri CGI discussi appresso, scomprimendo l'archivio fornito più avanti. Nel caso in cui i CGI sembrano non funzionare, è probabilmente possibile investigare sulla natura del problema, analizzando il contenuto dell'error.log, verso cui viene re-diretto lo standard error del CGI.
Ora verifichiamone il corretto funzionamento, referenziando la URI http://127.0.0.1/labint/primocgi.cgi, oppure invocandolo mediante la form di esempio mostrata a lezione. Eventualmente, qui troviamo un esempio del risultato che si dovrebbe ottenere. Notiamo che:
Come risultato della esecuzione del CGI possiamo trovare tutti i nomi ed i valori delle variabili di ambiente, come ad esempio alcuni degli header contenuti nella richiesta HTTP. In particolare, se abbiamo invocato il CGI a partire da una form usando il metodo GET, nella variabile QUERY_STRING possiamo osservare la componente della URI che contiene i nomi ed i valori dei parametri di chiamata, corrispondenti a quelli impostati mediante i controlli della form stessa. Pertanto, primocgi.cgi può essere utilizzato al posto del reale CGI che stiamo progettando, per verificare che almeno il passaggio dei parametri produca l'effetto desiderato.
Come illustrato nella parte di teoria, utilizzando il metodo POST, i valori impostati mediante i controlli della form sono passati nel body HTTP, e non nella URI: pertanto la modalità di accesso agli stessi usata da primocgi.cgi non è più idonea. Questo secondo esempio, che chiameremo secondocgi.cgi (ispirato da questo corso), ha appunto lo scopo di mostrare l'effetto di una tale chiamata.
#!/usr/bin/perl # # - secondocgi - visualizza il contenuto del body HTTP # use strict; print "Content-type: text/plain\n\n"; print "Salute a tutti.\n\n"; print "State osservando lo standard output prodotto da $0.\n"; print "\n o o o o o o o o o o o o o o o o o o o o \n\n"; print "Qui sotto, lo standard input ricevuto dal server web\n\n"; my $buffer; read (STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); print $buffer; print "\n\ned ora, alcune delle variabili di ambiente ricevute:\n\n"; my $key; foreach $key ('REQUEST_METHOD', 'QUERY_STRING', 'CONTENT_LENGTH', 'CONTENT_TYPE') { print "$key --> $ENV{$key}\n"; } |
L'istruzione read ha lo scopo di copiare nella variabile-stringa $buffer il contenuto del body della richiesta HTTP ricevuto via standard input, che ha la dimensione specificata dall'header HTTP Content-lenght, il cui valore è copiato da Apache nella omonima variabile di ambiente, che viene letta dal CGI accedendo all'hash %ENV.
Una form che invoca questo script è visibile nella parte di teoria, mentre qui possiamo osservare il risultato della invocazione. Come osserviamo, ora i parametri impostati mediante i controlli della form sono presentati nel body HTTP.
A prima vista, quella stringa del tipo nome1=valore1&nome2=valore2& etc etc ... non sembra molto masticabile da un programma, ma non è poi così difficile cavarne qualcosa di buono, come viene fatto con questo che chiameremo terzocgi.cgi, che funziona sia con il metodo GET che con il POST, e... si limita a farci vedere ciò che sa fare.
#!/usr/bin/perl # # - terzocgi - effettua la decodifica dei parametri ricevuti, sia con GET che con POST # use strict; print "Content-type: text/plain\n\n"; print "Salute a tutti.\n\n"; print "State osservando lo standard output prodotto da $0.\n"; print "\n o o o o o o o o o o o o o o o o o o o o \n\n"; print "Qui sotto, lo standard input ricevuto dal server web\n\n"; my $buffer; read (STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); print $buffer; print "\n\nPoi, alcune delle variabili di ambiente ricevute:\n\n"; my $key; foreach $key ('REQUEST_METHOD', 'QUERY_STRING', 'CONTENT_LENGTH', 'CONTENT_TYPE') { print "$key --> $ENV{$key}\n"; } print "\n\nInfine, la decodifica dei campi ricevuti\n\n"; my @data; # suddivide le coppie name/value tra loro if ( $ENV{ 'REQUEST_METHOD' } eq "GET" ) { @data = split /&/, $ENV{ 'QUERY_STRING' }; } elsif ( $ENV{ 'REQUEST_METHOD' } eq "POST" ) { @data = split /&/, $buffer; } else { print "Sono permessi solo i metodi GET e POST!"; exit; } my (%data, $value, $item); foreach $item ( @data ) { # Suddivide le coppie e traduce i + in spazi ($key, $value) = split /=/, $item; $key =~ tr/\+/ /; $value =~ tr/\+/ /; # Converte %XX da numeri hex ad alfanumerico $key =~ s/%([\da-f][\da-f])/pack("c",hex($1))/gei; $value =~ s/%([\da-f][\da-f])/pack("c",hex($1))/gei; # Assegna il valore ottenuto in corrispondenza della chiave relativa $data{$key} = $value; } foreach $key (keys %data) { print "$key ==>> $data{$key}\n"; } |
Notiamo che le conversioni effettuate sono presenti per de-codificare i parametri che sono stati inviati nel body HTTP con un MediaType MIME di tipo application/x-www-form-urlencoded, come possiamo verificare sniffando il traffico relativo, od anche aggiungendo al CGI la stampa della variabile di ambiente HTTP_CONTENT_TYPE. La form che invoca terzocgi.cgi è mostrata nella parte di teoria, e qui è mostrato il risultato della invocazione.
A partire dagli esempi forniti fin qui, ed usando il modulo Perl Email::Send, scriviamo un CGI in grado di recapitare per email l'ordine ricevuto. Il sapore non sarà un gran che, però farà un gran bell'effetto :-)
Per prima cosa, scarichiamo ed installiamo il modulo con le sue dipendenze mediante il comando sudo cpan Email::Send, che vi chiederà un pò di volte la vostra opinione, ma dovrebbe essere più che sufficiente che voi rispondiate sempre di si, premendo enter. Quindi, inseriamo in una pagina HTML il codice della form (sempre quello proposto a lezione), facendogli però ora invocare (mediante l'attributo action) il nuovo cgi, che chiamiamo vristo.cgi
Infine, editiamo il nostro nuovo cgi, vristo.cgi, derivato da quanto già realizzato con terzocgi, il cui codice (che fa uso di espressioni regolari) è riportato sotto. Dopo aver salvato vristo.cgi nella directory public_html ed aver correttamente configurato i permessi, possiamo provare ad immettere un ordine nella form soprastante, e verificare se l'email ci arriva! Ad ogni modo, anche stavolta riportiamo l'esito della esecuzione.
Dato che la spedizione della email avverrà a carico del server SMTP presente sul nostro stesso computer, accertiamoci che questo sia attivo, e ricordiamoci di usare un indirizzo email di destinazione. Qualora si desideri recapitare il pasto ad una email corrispondente ad un ISP esterno, ricordarsi di modificare il server SMTP in quello di Outbound.
Nell'archivio cgi.tar si può trovare ilcodice discusso in questa pagina. Per far funzionare gli esempi che invocano i cgi, scomprimere l'archivio nella directory public_html del proprio computer, a meno che non ne sia stata configurata una diversa, ed assicurarsi di aver impostato correttamente i permessi di accesso al file.
Lo scenario generale della infrastruttura di sicurezza web è particolarmente ben riassunto nella documentazione di Apache, che offre questi servizi mediante il modulo ssl. Inoltre, usare lo stesso server Apache per offrire contemporaneamente sia l'HTTP su porta 80, che l'HTTPS su porta 443, richiede la configurazione di due diversi Virtual Host, di cui il primo è quello (default) utilizzato finora, ed il secondo è quello che ci accingiamo a definire nel file /etc/apache2/sites-available/secure. Pertanto, impartiamo ora i comandi
sudo a2enmod ssl sudo cp /etc/apache2/sites-available/default /etc/apache2/sites-available/secure sudo a2ensite secure sudo gedit /etc/apache2/sites-enabled/secure |
che abilitano sia il modulo ssl che il sito secure, il cui file di configurazione è ottenuto a partire da una copia di quello di default. L'ultimo comando della serie, ci consente di editare la configurazione del file che definisce il funzionamento del server HTTPS. Innanzitutto, modifichiamo le prime due linee, in modo da dichiarare esplicitamente l'uso della porta 443, e quindi abilitiamo l'uso di SSL su questa porta, mediante la direttiva SSLEngine, in modo che ora l'inizio del file secure appaia come segue:
NameVirtualHost *:443 <VirtualHost *:443> SSLEngine on |
Se ora proviamo a rilanciare Apache (sudo /etc/init.d/apache2 restart), questo si rifiuta di andare in esecuzione, notificando nel file di log il messaggio: Server should be SSL-aware but has no certificate configured [Hint: SSLCertificateFile]. Infatti, occorre specificare il certificato e la chiave da usare, inserendo di seguito alla direttiva SSLEngine, le direttive
SSLCertificateFile
/etc/ssl/certs/ssl-cert-snakeoil.pem SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key |
Stavolta Apache accetta di partire, ed invocando una delle URI servite (es https://localhost), la pagina arriva, e il campo di inserimento indirizzo si colora di giallo, e si arricchisce del disegno di un lucchetto, a segnalare la sicurezza della navigazione. Ma... prima di arrivare a questo bel risultato, nel file di log abbiamo osservato il messaggio
RSA server certificate CommonName (CN) `terranova' does NOT match server name!? |
ed infatti, il certificato che abbiamo utilizzato non è rilasciato al nome a dominio che viene servito da Apache. Questo fatto comporta che anche il browser, prima di visualizzare la pagina richiesta, notifica alcune finestre di avvertimento, mediante le quali dichiara
Mentre il primo fatto è evidentemente legato alla assenza (presso il browser) del certificato della CA che ha firmato il certificato del server, il secondo è risolvibile avendo l'accortezza di richiedere un certificato per il quale il Common Name corrisponda esattamente al nome a domino per cui si intenderà usarlo. Per questo, possiamo generare una richiesta di certificato e firmarla, usando ad es. lo strumento gnoMint, ed utilizzando per la firma la chiave privata di labsoftelCA, che importiamo in gnoMint. Una volta esportati certificato e chiave privata intestati al CN uguale al nostro dominio, sostituiamo (nel path dichiarato mediante la direttiva SSLCertificateFile presente nel file secure) questi nuovi valori, a quelli relativi a ssl-cert-snakeoil, e ri-lanciamo Apache. Sullo schermo, si sviluppa il dialogo
alef@alef:~$ sudo /etc/init.d/apache2 restart * Starting web server apache2 Apache/2.2.4 mod_ssl/2.2.4 (Pass Phrase Dialog) Some of your private key files are encrypted for security reasons. In order to read them you have to provide the pass phrases. Server alef-laptop.softel:443 (RSA) Enter pass phrase: OK: Pass Phrase Dialog successful. [ OK ] alef@alef:~$ |
che rappresenta la richiesta, da parte di Apache, di conoscere la passphrase con cui è crittografata la chiave privata associata al nuovo cerificato usato, che nell'esempio precedente era invece lasciata in chiaro.
Finalmente immettendo la URI https://localhost, non si ottiene più l'avvertimento relativo al dominio sbagliato! Non resta quindi che importare nel browser il certificato della CA che ha firmato il certificato con cui abbiamo configurato Apache. Cliccando sul link del certificato appena fornito, questo viene visualizzato dal Browser, ma non importato dallo stesso. Pertanto, occorre prima salvarlo in un file, e quindi importarlo in modo esplicito, ossia (in firefox)
Alernativamente, si può configurare Apache affinchè quando uno UserAgent invoca la URI del certificato, questo venga automaticamente importato dal browser, senza la necessità dei passi ora illustrati. A questo scopo, occorre modificare il file /etc/apache2/apache2.conf, inserendo la direttiva
AddType application/x-x509-ca-cert pem |
in modo che il file con estensione .pem sia inviato con il MIME-Type corretto.
Dal TCP al VoIP, dal DNS all'Email alla crittografia, tutto ciò che accade
dietro le quinte di Internet, completo di cattura del traffico.
Scopri
come effettuare il download,
ricevere gli aggiornamenti,
e contribuire!