<- SL: Intro - Copertina - SL: Postfix -> |
Sistemi Liberi
L'articoloSi descrivono alcune caratteristiche avanzate del server WEB Apache, che includono la gestione di siti su host virtuale, la multiutenza, il supporto per la programmazione CGI. Vengono discussi i problemi di configurazione e le soluzioni diagnostiche. Vengono presentati una serie di test da svolgere sia per controllare il funzionamento del server, sia a scopo didattico per familiarizzare con la soluzione proposta. |
http.conf
in tutta la sua gloriaApache è un potente WEB server multipiattaforma. Le statistiche dicono che è il più utilizzato al mondo, e tutto lascia prevedere che lo sarà ancora per qualche anno.
Apache è disponibile nel sito ufficiale www.apache.org
, insieme ai
sorgenti e a tutta la documentazione. Tutte le distribuzioni di GNU/Linux lo
includono come pacchetto, tipicamente già compilato e
configurato: è sufficiente una installazione base del sistema
operativo, ed anche l'utilizzatore più sprovveduto si ritrova
con un WEB server installato (magari a sua insaputa...).
Le cose vanno diversamente quando si desidera configurare Apache per uso di produzione: diventa allora difficile orientarsi tra la miriade delle sue opzioni di configurazione, e la documentazione ufficiale non aiuta il neofita. In particolare in questo articolo vogliamo configurare Apache per supportare la multiutenza e raggiungere i seguenti obiettivi: gestire vari domini WEB; isolare i programmi CGI dei vari siti tra di loro; dotare gli utenti ciascuno della propria home page.
Queste note sono il frutto di alcuni mesi di lavoro passati a spulciare nella documentazione del programma e a fare prove su varie distribuzioni GNU/Linux, principalmente Red Hat 6.2 e Red Hat 7.1, che includono la versione 1.3 di Apache. I suggerimenti e le soluzioni che descrivo si applicano ovviamente anche ad altre distribuzioni, che potranno differire al più per la scelta dei path di installazione e per il diverso packaging dei vari componenti (modulo base, moduli supplementari, ecc.).
Non ho certo intenzione di scrivere un tutorial per Apache o un ennesimo manuale. Si tratta semplicemente della descrizione di esperienze che possono interessare gli iniziati che, una volta eseguita con successo l'installazione base di Apache, desiderano approfondirne alcune funzionalità avanzate.
Non ci inoltreremo qui nelle procedure di ricompilazione del sorgente del programma, nè faremo altre cose più tediose e "difficili": ci limiteremo a modificare il file di configurazione di Apache ed eventualmente interverremo su qualche altro file di sistema usando pratiche consuete di configurazione. Riporremo la nostra fiducia nel rivenditore della distribuzione, che di solito ha cura di rendere disponibile sul suo sito WEB l'ultima versione di Apache corretta rispetto ai bug e ai problemi di sicurezza. Non ci interessa invece installare l'ultimissima versione, poiché ci concentreremo sugli aspetti generali della gestione del server.
Ultima annotazione prima di cominciare: teoricamente il system administrator e il WEB administrator sono due ruoli distinti. In realtà l'intreccio di competenze è tale che le due figure finiscono per coincidere. E' molto probabile che la configurazione del WEB server coinvolga parecchi settaggi del sistema, della posta elettronica, del DBMS, dei permessi e degli utenti, sicchè separare le due figure diventa davvero difficile. Perciò qui userò i due termini indifferentemente.
Assicuriamoci innanzitutto che Apache sia installato ed in esecuzione. E' interessante nonché divertente per il sistemista conoscere alcuni modi alternativi per fare ciò:
1. Ordine e disciplina - Seguire la via maestra
GNU/Linux segue le convenzioni System V per l'inizializzazione del sistema.
Quindi dovrebbe essere disponibile un piccolo script di shell che si incarica
di avviare, fermare e verificare lo stato del server. Lo script esegue una
di queste funzioni a seconda che sia stato invocato con il parametro
start
, stop
oppure status
;
esiste poi anche il parametro restart
che ha l'ovvio significato.
Sulla distribuzione Red Hat 6.2 il comando è
# /etc/rc.d/init.d/httpd status httpd (pid 9702 9701 9698) is running... |
mentre per la Red Hat 7.1 il path differisce leggermente:
# /etc/init.d/httpd status httpd (pid 9702 9701 9698) is running... |
L'output conseguente dovrebbe confortarci sul fatto che Apache sia regolarmente in esecuzione. Il messaggio esatto dipende da come è stato scritto questo script, che invito ad andare a vedere.
2. Il Riduzionista - In Unix è tutta una questione di file e di processi
Sappiamo che Unix, GNU/Linux e gli altri sistemi operativi affini sono
concepiti con la filosofia del meccano: pochi strumenti, ma buoni. File
e processi sono il fondamento del sistema, il resto è il sale e
il pepe. Il sistemista-riduzionista non si fida degli script automatici,
ma vuole andare all'origine delle cose, e va a guardare direttamente
i processi in esecuzione:
# ps ax | grep httpd 9698 ? S 0:00 /usr/sbin/httpd parametri vari 9701 ? S 0:00 /usr/sbin/httpd parametri vari 9702 ? S 0:00 /usr/sbin/httpd parametri vari 9706 pts/2 S 0:00 grep httpd |
Il sistemista-riduzionista è previdente: sa che sulla sua
macchina oberata di lavoro sono in esecuzione centinaia di processi,
per cui inserisce un sano grep
che seleziona solo le righe
che contengono la stringa httpd
. Guardacaso, queste righe
sono proprio quelle relative ai processi di Apache.
3. L'hacker che c'è in noi - Emerge il lato oscuro
Ragioniamo: se un server di rete fornisce servizi in rete, il
modo migliore per verificarne il funzionamento è provare
direttamente questi servizi. Apriamo allora il nostro browser WEB
preferito e puntiamolo sull'URL http://localhost
. Il bravo
sistemista/hacker con tendenze riduzioniste usa Telnet come browser WEB:
# telnet localhost 80 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. GET / HTTP/1.0 qui premi ENTER due volte HTTP/1.1 200 OK Date: Mon, 10 Dec 2001 21:50:38 GMT Server: Apache/1.3.19 (Unix) (Red-Hat/Linux) Last-Modified: Sat, 11 Aug 2001 19:58:38 GMT ETag: "49183-20e-3b758e6e" Accept-Ranges: bytes Content-Length: 526 Connection: close Content-Type: text/html <HTML> la pagina HTML che non ci interessa </HTML> Connection closed by foreign host. # |
Come al solito, solo il comando evidenziato in grassetto deve essere impartito
a tastiera, il resto sono le risposte del sistema. Notiamo che il comando
GET
deve essere scritto proprio in lettere maiuscole, e che
occorre premere due volte il tasto ENTER prima che il server risponda:
la riga vuota è il modo in cui il client (cioè il nostro
Telnet) spiega al server che ha concluso la sua domanda. Infatti, vedremo
che il client ha la possibilità di specificare vari altri parametri
su altrettante righe.
Come è facile immaginare, il comando GET
serve per
richiedere un documento; a seguire bisogna specificare la parte di path
file dell'URL del documento, essendo per convenzione /
la
richiesta della home page, o pagina default, o pagina indice del sito.
Con lungimiranza, il protocollo HTTP richiede anche di specificare
esplicitamente il protocollo e la versione.
Trucco |
---|
Se il terminale che usi non supporta lo scrolling,
sarà difficile riuscire a leggere le prime righe, che sono le
più interessanti. Rimedi possibili: usare script ;
richiedere una pagina deliberatamente errata al posto di / ,
ad esempio /xxx ;
infine, usare un comando come questo:# (echo -ne "GET / HTTP/1.0\n\n"; sleep 2) \ > | telnet localhost 80 | head -20 In questo modo appariranno solo le prime 20 righe del dialogo con il server, indipendentemente dalla effettiva lunghezza della risposta. Usando lo stesso principio si può andare ad esplorare in giro i vari server, e verificare di persona quali sono i server più usati: interessante, vero? |
A questo punto dovremmo avere la certezza che l'installazione di base di Apache è a posto. Per ora Apache ritorna la pagina di default predefinita dalla distribuzione, ma questo ci va bene perché stiamo per cambiare un po' tutto...
Di base il server Apache risponde ad un unico indirizzo IP e ad
un unico dominio, cioè gestisce un solo sito WEB del tipo
www.azienda.it
o una cosa del genere. Gli ISP hanno
invece la necessità di gestire parecchi domini. Le aziende
più avanzate informaticamente sul fronte Internet possono
voler creare siti WEB distinti per le diverse aree funzionali, come
support.azienda.it
, oppure desiderano ospitare siti WEB
delle ditte affiliate. Con il protocollo HTTP versione 1.0 era necessario
dotare ogni sito WEB di un indirizzo IP distinto, anche se poi questo
indirizzo si riferiva alla stessa macchina fisica. In questo consiste
il meccanismo di host virtuale basato sull'indirizzo IP.
Le cose funzionavano (e possono funzionare tuttora) così: se un
navigatore si collegava all'URL www.azienda.it
, il browser
risolveva il nome in un certo numero IP 11.22.33.44
, ed
è a questo server che il browser inviava la richiesta; quando
il navigatore si collegava a support.azienda.it
, il browser
risolveva il nome in un indirizzo IP diverso 55.66.77.88
al quale rispondeva magari la stessa macchina, ma lì il server
poteva discriminare il sito WEB richiesto in base all'indirizzo IP.
Per ridurre lo spreco di preziosi indirizzi IP e tutte le inevitabili complicazioni tecniche, burocratiche e di costi, il protocollo HTTP versione 1.1 ha aggiunto la funzionalità di host virtuale basato sul nome. Il meccanismo è abbastanza semplice: il browser segue la stessa trafila di prima, ma nella richiesta della pagina specifica anche per esteso il nome del dominio richiesto. Il server WEB, anche se dotato di un solo indirizzo IP, ha così modo di riconoscere quale sito WEB tra quelli ospitati è stato effettivamente richiesto. In questo consiste il meccanismo di host virtuale basato sul nome. E' ovvio che questo sistema richiede la collaborazione del browser per funzionare. Comunque, tutti i browser moderni supportano HTTP 1.1, tanto che il meccanismo degli host virtuali è ampiamente utilizzato. Forse tutto questo discorso suona un po' ingarbugliato, ma vedremo subito degli esempi molto concreti che dovrebbero chiarire bene come vanno le cose.
Dunque, Apache include il supporto per gli host virtuali basati sul nome di dominio, previsti dal protocollo HTTP versione 1.1. Questo ci permette di ospitare sulla nostra macchina un numero arbitrario di siti WEB distinti utilizzando sempre un solo indirizzo IP. Le specifiche tecniche dell'implementazione che andremo a fare sono le seguenti:
The "Casa" ISP Specs | ||
---|---|---|
IP Address: | 127.0.0.1 | |
WEB Sites: | Domain | DocumentRoot |
localhost.localdomain | /home/httpd/html | |
www.tizio.casa | /home/tizio/public_html | |
www.caio.casa | /home/caio/public_html |
L'indirizzo IP non è scelto a caso: si tratta dell'indirizzo di loop-back previsto per scopi di test e disponibile su ogni macchina. Altro aspetto positivo di questa scelta: non servono schede di rete e non serve essere connessi ad Internet.
Per quanto riguarda i domini gestiti, sono ovviamente nomi di
fantasia che possiamo inserire nell'elenco degli host name del file
/etc/hosts
, che andiamo subito a modificare in modo che
appaia così:
127.0.0.1 localhost.localdomain localhost 127.0.0.1 www.tizio.casa 127.0.0.1 www.caio.casa |
In genere la prima riga è già predisposta, mentre le altre due sono quelle appena aggiunte. A questo punto i processi che avvieremo saranno in grado di risolvere i nuovi nomi di dominio.
La strategia che seguiremo comporta la creazione di un normale account
per ciascun dominio gestito. Vogliamo insomma che ogni dominio sia gestibile
da un utente distinto, che vi può accedere in Telnet o FTP come
di consueto. Nella home directory, ciascun utente sarà libero di gestire
il suo sito in totale libertà.
Per creare l'utente Tizio facciamo questo:
# useradd tizio -g users # passwd tizio Changing password for user tizio New UNIX password: ****** Retype new UNIX password: ****** passwd: all authentication tokens updated successfully # chmod u=rwx,g=,o=x /home/tizio # mkdir /home/tizio/public_html # chown tizio:users /home/tizio/public_html # chmod u=rwx,g=,o=x /home/tizio/public_html |
Per i più pigri, spiego a parole quello che abbiamo fatto:
il nuovo utente possiede una normale home directory posta in /home/tizio
; dentro a questa directory questo signore potrà lavorare normalmente
come un qualsiasi utente. In più abbiamo creato una sotto-directory
di nome public_html
che conterrà la parte pubblica
del sito WEB del sig. Tizio. In altri termini, andremo a configurare Apache
in modo che gli URL del tipo http://www.tizio.casa/x
vengano
tradotti nel pathfile /home/tizio/public_html/x
.
Ci sono però due aspetti molto importanti da considerare:
users
: questo devia dallo schema generale della distribuzione
Red Hat, che per default crea un nuovo gruppo per ogni utente.
o+x
per consentire ad Apache di
attraversarla per raggiungere le pagine WEB dentro alla subdirectory
public_html
. I permessi assegnati sono:
Permessi di /home/tizio | ||||||||
---|---|---|---|---|---|---|---|---|
tizio |
users |
|||||||
r |
w |
x |
- |
- |
- |
- |
- |
x |
Conviene spendere due parole sul significato di quanto fatto riguardo ai permessi di accesso, perché l'argomento è un po' delicato, essendo coinvolti i problemi di sicurezza e riservatezza. Fintanto che il server WEB è al servizio di un'unica azienda, la separazione degli spazi di lavoro semplifica la gestione e riduce le interferenze. Tuttavia se il server deve essere reso disponibile ad altri soggetti sui quali non si ha pieno controllo, allora le cose vanno studiate più attentamente.
Spieghiamo innanzitutto cosa intendiamo con il termine identità di un processo. Ogni processo ha varie caratteristiche: ad esempio una è il PID (process identifier), il numero univoco che identifica il processo nel sistema. Altre due caratteristiche molto importanti sono l'UID e il GID del processo, cioè il numero dell'utente e il numero del gruppo che contraddistinguono il processo. Sono questi valori che il kernel esamina prima di dare il permesso a un processo di accedere ad una risorsa, come un file o un device. Richiamiamo brevemente i criteri seguiti dal kernel per decidere se autorizzare o meno una certa operazione richiesta da un processo su di un file:
|
Ricordo che Apache gira con l'identità UID:GID = nobody:nobody
su Red Hat 6.2, e con l'identità apache:apache
su
Red Hat 7.1. Questi parametri vengono impostati nella configurazione
predefinita con le direttive User
e Group
nel file di configurazione httpd.conf
.
La conseguenza di tutto questo discorso è che Apache potrà
entrare nelle directory degli utenti Tizio e Caio grazie al permesso
o+x
; inoltre Apache potrà leggere tutti e soli i file
accessibili ad "other". Viceversa, gli altri utenti del sistema, i cui processi
operano con GID = users
, si scontrano con il secondo criterio
di validazione, e non possono entrare nella directory.
ATTENZIONE! |
---|
Il modello di sicurezza per i permessi dei file e delle directory
non è l'unico possibile, ma forse è il più semplice
da applicare e da gestire. Lascia tuttavia qualche perplessità
il dover rendere accessibile a "other" la propria home directory.
Esiste almeno un'altra soluzione che si basa sulla creazione di un gruppo
per ciascun utente, paradigma scelto per default dalla distribuzione
Red Hat. Le home directory degli utenti vanno poi rese attraversabili
dal proprio gruppo. L'utente del server ( |
E finalmente arriviamo alla configurazione di Apache. Il file di
configurazione è httpd.conf
, che nella distribuzione
Red Hat 6.2 e 7.1 si trova in /etc/httpd/conf/httpd.conf
.
Raccomando a questo punto di fare una bella copia del file prima che
ci mettiamo su le manacce: è utile avere come riferimento un file
di configurazione che sappiamo essere funzionante.
Al termine del file httpd.conf
c'è la sezione
destinata agli host virtuali. Assicuriamoci che sia presente la direttiva
NameVirtualHost 127.0.0.1:80 |
che attiva la funzionalità degli host virtuali basati sul nome
sulla solita porta 80 del nostro server. Creiamo il virtual host per il
dominio www.tizio.casa
:
<VirtualHost 127.0.0.1:80> ServerName www.tizio.casa DocumentRoot /home/tizio/public_html ServerAdmin tizio@localhost CustomLog logs/www.tizio.casa-access_log common ErrorLog logs/www.tizio.casa-error_log </VirtualHost> |
Per gli altri domini virtuali creiamo altrettanti utenti e altre sezioni VirtualHost similari, facendo attenzione a modificare di conseguenza il dominio, il path e l'indirizzo di posta, evidenziati in grassetto nell'esempio. Come ServerAdmin ho indicato in realtà l'utente tizio invece che il vero WEB administrator: di solito i problemi legati a un sito WEB sono locali a quel sito (pagine mancanti, CGI non funzionanti, ecc.) piuttosto che responsabilità del WEB administrator.
# su - tizio $ echo "<html><body><h1>Benvenuti in www.tizio.casa</body></html>" > public_html/index.html $ [CTRL-D] # su - caio $ echo "<html><body><h1>Benvenuti in www.caio.casa</body></html>" > public_html/index.html $ [CTRL-D] |
Non ci resta che riavviare Apache. Il comando da dare su Red Hat 6.2 è
# /etc/rc.d/init.d/httpd restart |
mentre su Red Hat 7.1 è
# /etc/init.d/httpd restart |
Se qualcosa è andato storto, c'è sempre il log file in
/var/log/httpd
. Mentre si fanno queste prove consiglio
di tenere sempre aperta una finestra dedicata alla visualizzazione dei
messaggi di log tramite il comando tail -f /var/log/httpd/*
.
Se è tutto ok, puntiamo il WEB browser
sull'URL http://www.tizio.casa
e poi su
http://www.caio.casa
. Ecco quello che io ho ottenuto:
$ (echo -ne "GET / HTTP/1.1\nHost: www.tizio.casa\n\n" > sleep 2) | telnet localhost 80 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. HTTP/1.1 200 OK Date: Wed, 11 Dec 2001 21:33:20 GMT Server: Apache/1.3.19 (Unix) (Red-Hat/Linux) Last-Modified: Mon, 10 Dec 2001 07:55:23 GMT ETag: "fa7b-43-3c146a6b" Accept-Ranges: bytes Content-Length: 67 Content-Type: text/html <html><body>Benvenuto in www.tizio.casa</body></html> Connection closed by foreign host. |
Prova questo comando:
# (echo -ne "GET / HTTP/1.0\n\n"; sleep 2) \ > | telnet localhost 80 | head -20 |
esso dovrebbe ritornare il primo virtual host (che al momento dovrebbe
essere quello del sig. Tizio). Possiamo provare anche ad aggiungere il
virtual host localhost.localdomain
come primo virtual host
e ripetere la prova.
Adesso sfruttiamo il protocollo HTTP v. 1.1 per ritornare gli altri
virtual host. Per il sig. Caio si tratta di aggiungere la riga Host:
www.caio.casa
nella nostra interrogazione:
# (echo -ne "GET / HTTP/1.0\nHost: www.caio.casa\n\n"; sleep 2) \ > | telnet localhost 80 | head -20 |
Le stesse prove si possono fare anche da un browser WEB "normale", ma si perde tutto il gusto...
Con questo abbiamo concluso il nostro tour sul meccanismo dei
domini virtuali. In una situazione reale avremo che i
domini www.tizio.casa
e www.caio.casa
saranno registrati su un qualche DNS (e non nel nostro file
/etc/hosts
), e l'indirizzo IP da assegnare ai VirtualHost
sarà l'indirizzo pubblico assegnato alla nostra macchina (e non
l'indirizzo di loopback 127.0.0.1).
Il WEB sarebbe ben poca cosa senza la possibilità di generare dinamicamente le pagine richieste e senza quella di interagire con il client remoto. Il protocollo HTTP da una parte, e le tecnologie CGI e SSI di Apache dall'altra sono gli strumenti che forniscono queste possibilità. Apache viene corredato anche con vari interpreti incorporati, come ad esempio PERL, che permettono una esecuzione molto efficiente di codice di scripting incorporato all'interno delle pagine WEB.
Spendiamo due parole per chiarire cos'è un programma CGI: si
tratta appunto di un programma, realizzabile con un qualsiasi linguaggio
di programmazione o di scripting, che può essere invocato come
una pagina WEB o come gestore di un form
HTML; il WEB
server, anziché ritornare al client il file del programma, lo esegue.
L'output generato da questo programma è poi quello che il server
restituirà al client, sia esso una pagina HTML, un'immagine
GIF o qualsiasi altro tipo di informazione.
Le specifiche dell'interfaccia CGI dicono anche come il programma CGI
possa ricevere input dal client, chiudendo quindi il cerchio. Rimandiamo
al prossimo articolo la discussione dettagliata della tecnologia CGI,
delle possibilità che essa offre, e dei possibili utilizzi pratici.
Tuttavia, nuove possibilità significano nuovi problemi per l'amministratore del sistema. Cerchiamo di capire meglio il problema introducendo l'importantissima questione della identità dei processi CGI.
Abbiamo detto che il server Apache è un processo avviato
con l'identità UID:GID = nobody:nobody
nella
distribuzione Linux Red Hat 6.2, oppure con l'identità UID:GID =
apache:apache
nella distribuzione Linux Red Hat 7.1. Con
altre distribuzioni di GNU/Linux questa identità può cambiare,
ma resta il fatto che Apache funziona con questa unica identità.
L'identità del processo impone anche i limiti per quanto riguarda
l'accesso alle risorse del sistema:
I programmi CGI avviati da Apache ereditano l'identità di processo di quest'ultimo anche quando appartengono ad utenti diversi. Lo stesso discorso vale per gli interpreti incorporati. Quindi, ad esempio, le funzioni per gestire i file richiedono di impostare opportunamente i permessi e la proprietà della directory dove il file risiede, oltre che i permessi e la proprietà del file stesso. Non è possibile creare file in directory non accessibili al server, né è possibile leggere file non leggibili per il server.
Supponiamo ora che i programmi CGI prodotti dal sig. Tizio e dal sig. Caio
abbiano necessità di svolgere varie funzioni: esattamente come
qualsiasi altro programma, avranno bisogno di leggere e scrivere
file, accedere a un DB e altre cose del genere. Purtroppo, siccome essi
verranno eseguiti con la stessa identità, saremo costretti
ad assegnare diritti di accesso in comune a tutti i CGI. Per essere più
espliciti: a livello di CGI gli utenti Tizio e Caio potranno pasticciare
liberamente sugli stessi file. Il sig. Tizio non potrà leggere o
scrivere un suo file riservato senza che questo non sia leggibile anche
al sig. Caio. Il Sig. Tizio vorrà accedere a un DBMS riservato
specificando una password, ma se usa un linguaggio di scripting il suo
programma deve necessariamente essere leggibile ai programmi CGI degli
altri utenti, che potranno così carpirgli facilmente la password.
Il sig. Tizio non potrà leggere o scrivere file nella propria home
directory (al di fuori di public_html
) senza dover
pericolosamente rilassare i diritti di accesso al suo account.
In altri termini, abbiamo un sistema multiutente che, a livello del server
WEB, diventa monoutente...
Pensiamo invece a quanto sarebbe logicamente bello e praticamente utile se i programmi CGI di un utente fossero eseguiti con l'identità dell'utente stesso: il programma avrebbe pieno accesso al suo account e potrebbe leggere e scrivere file; i CGI non sarebbero leggibili nè copiabili dagli altri utenti, anche quando si trattasse di script; l'autenticazione al DBMS potrebbe essere automatica perchè basata sulla identità del processo; ecc. Tutte queste possibilità offrono agli utenti molte opportunità di sfruttare in modo creativo le tecnologie del WEB.
Esiste anche un rovescio della medaglia: l'esecuzione di programmi CGI qualora siano realizzati come script richiede più tempo per l'avviamento rispetto agli stessi programmi eseguiti dagli interpreti incorporati in Apache. Del resto, se un sito WEB comporta realmente un grande flusso di traffico, forse vale la pena di dedicare ad esso un server completo, per cui l'obiezione è valida solo in parte.
La documentazione di Apache è estremamente prudente in proposito: SUEXEC è uno strumento potente, ma altrettanto potentemente insidioso per la sicurezza dalle intrusioni. L'esperienza del passato ha lasciato alcune vittime, ma gli sviluppatori di Apache hanno studiato contromosse altrettanto diaboliche. Rimando perciò a questa fonte per la discussione della questione. Noi sfideremo intrepidi il rischio, ansiosi di esplorare le nuove dimensioni che si dischiuderanno.
Il famigerato SUEXEC di Apache non è altro che un programmino
di una decina di KB che si occupa di avviare i CGI per conto di Apache,
conferendo però al processo così avviato l'identità
del suo proprietario. Quindi, ad esempio, i CGI del sig. Tizio verranno
eseguiti con l'identità UID:GID = tizio:users
.
Il SUEXEC si preoccupa di garantire che il processo avviato sia legittimo.
La tabellina qui sotto riassume le identità dei vari processi nel
caso di un programma CGI mio.cgi
avviato per conto del sig. Tizio:
Processo | UID | GID |
---|---|---|
httpd | nobody (RH6.2)apache (RH7.1) | nobody (RH6.2)apache (RH7.1) |
suexec | root | nobody (RH6.2)apache (RH7.1) |
mio.cgi | tizio | users |
La distribuzione Red Hat viene con il programma suexec
già pronto, ma disattivato. Inoltre, il programma prevede,
tra gli infiniti controlli, che il CGI avviato si trovi dentro alla
directory /home/httpd
(o in altra directory prevista dalla
distribuzione), sicché non è adatto alla nostra situazione.
Se la tua distribuzione non include il programma
/usr/sbin/suexec
(è infatti questo il nome al secolo
dell'innominabile), allora dovrai proprio procedere alla compilazione dei
sorgenti di tutto Apache, oltre che scaricare la patch SUEXEC dal
sito ufficiale. In questo caso seguire scrupolosamente le indicazioni
fornite, eccetto che il pathfile della directory dei CGI dovrà
essere /home
. Buon lavoro e buona fortuna. Ci rivediamo al
prossimo paragrafo sugli ScriptAlias.
Se invece sei un pigro, troverai più veloce ed indolore la ricetta che segue, valida per le distribuzioni Red Hat 6.2 e 7.1.
suexec è abilitato per eseguire i CGI solo nella directory
/home/httpd/
(RH6.2) o nella dir. /var/www
(RH7.1): per abilitare il meccanismo nelle dir. degli utenti
/home
è necessario ricompilare il programma suexec
cambiando opportunamente la configurazione hard-coded. Poiché ci
secca di intraprendere questo lungo e impervio cammino, scegliamo una
scorciatoia sporca: andremo a cambiare il codice del programma con un
editor binario! Io ho usato l'editor del potente Midnight Commander:
/usr/sbin/suexec
da mc, e usa l'editor binario
integrato di Midnight Commander.
/home/httpd/html
e inserisci uno zero al posto del secondo
slash: in questo modo per il programma la stringa risulterà
troncata a solo /home
. Per la RH7.1 invece, individua la
stringa /var/www/
e modificarla in /home
,
ricordando sempre di mettere un byte zero in fondo, e sempre senza
cambiare la lunghezza complessiva del file. In entrambi i casi, i
caratteri d'avanzo dopo il byte nullo inserito sono irrilevanti. Un
errore in questa fase renderebbe inutilizzabile il file suexec. Ne hai
già fatto una copia, vero?
nobody
,
mentre nella RH7.1 è apache
.
Se nella tua configurazione personalizzata hai modificato l'identità
del server Apache, dovrai individuare questa stringa e modificarla
di conseguenza, sempre nello stesso modo. Di norma non dovrebbe
però essere necessario.
Per la RH6.2 e RH7.1 è sufficiente attivare il bit SUID del programma suexec, e magari restringere il set di permessi inutilmente ampi che la distribuzione attribuisce al file. Per la RH6.2:
# chown root:nobody /usr/sbin/suexec # chmod u=xs,g=x,o= /usr/sbin/suexec |
# chown root:apache /usr/sbin/suexec # chmod u=xs,g=x,o= /usr/sbin/suexec |
# ls -l /usr/sbin/suexec ---s--x--- 1 root nobody 9488 Aug 12 14:19 /usr/sbin/suexec |
# ls -l /usr/sbin/suexec ---s--x--- 1 root apache 10976 Aug 12 13:05 /usr/sbin/suexec |
Se non corrispondono, agire con chown
e chmod
opportunamente.
Non rimane che riavviare Apache. Nel log file /var/log/httpd/access_log
dovrebbe apparire:
[notice] caught SIGTERM, shutting down [notice] Apache/1.3.19 (Unix) (Red-Hat/Linux) configured -- resuming normal operations [notice] suEXEC mechanism enabled (wrapper: /usr/sbin/suexec) |
La riga che ci interessa è l'ultima, e ci rassicura sul fatto che Apache abbia digerito il nostro suexec!
Il nome di questa direttiva di Apache è fuorviante, perché
lascia pensare che riguardi solo gli script. In realtà si tratta
di una regola di matching che permette ad Apache di stabilire se un certo
file va considerato come documento o come programma CGI. Nel primo caso
Apache tenterà di riconoscerne il tipo e lo restituirà
al client che lo ha richiesto. Nel secondo caso Apache lancerà il
programma come CGI; siccome abbiamo appena attivato il supporto SUEXEC,
il programma assumerà l'identità del proprietario.
Esiste anche ScriptAliasMatch
, che permette di utilizzare
anche espressioni regolari: ne vedremo degli esempi.
Le direttive ScriptAlias
e ScriptAliasMatch
possono apparire nella configurazione globale del server, e anche nelle
sezioni dei VirtualHost. La scelta più tradizionale prevede
di collocare i CGI nella directory /cgi-bin
della root
directory del sito. In tal caso come direttiva globale metteremo
ScriptAlias /cgi-bin/ /home/httpd/cgi-bin/ |
<VirtualHost 127.0.0.1:80> ServerName www.tizio.casa User tizio Group users DocumentRoot /home/tizio/public_html ServerAdmin tizio@localhost ScriptAlias /cgi-bin/ /home/tizio/public_html/cgi-bin/ CustomLog logs/www.tizio.casa-access_log common ErrorLog logs/www.tizio.casa-error_log </VirtualHost> |
Le direttive User
e Group
dicono al programma
suexec
quale dovrebbe essere l'identità del
programma CGI: suexec
esegue poi un controllo stringente
sulla proprietà del file e sui suoi diritti di accesso prima di
autorizzarne l'esecuzione con l'identità stabilita.
La direttiva ScriptAlias
dichiara dove
si trova la directory dei CGI, e mappa gli URL del tipo
http://www.tizio.casa/cgi-bin/xxx
nel pathfile
/home/tizio/public_html/cgi-bin/xxx
.
Più potente la direttiva ScriptAliasMatch
: diventa
possibile utilizzare espressioni regolari per identificare i programmi
CGI. Per fare un esempio, possiamo istruire Apache a riconoscere
come programma CGI ogni file il cui nome termina con la stringa
".cgi
". A questo scopo basta una direttiva globale come
questa:
ScriptAliasMatch /(.*)\.cgi /home/httpd/html/$1.cgi |
<VirtualHost 127.0.0.1:80> ServerName www.tizio.casa User tizio Group users DocumentRoot /home/tizio/public_html ServerAdmin tizio@localhost ScriptAlias /cgi-bin/ /home/tizio/public_html/cgi-bin/ ScriptAliasMatch /(.*)\.cgi /home/tizio/public_html/$1.cgi CustomLog logs/www.tizio.casa-access_log common ErrorLog logs/www.tizio.casa-error_log </VirtualHost> |
Questa volta la direttiva ScriptAliasMatch
mappa URL
del tipo http://www.tizio.casa/x/y/z.cgi
nel pathfile
/home/tizio/public_html/x/y/z.cgi
, ed istruisce anche Apache sul fatto
che questo file sia un CGI da eseguire.
Avrai visto qualche volta un URL del tipo
www.qualcosa.org/~tizio
dove tizio
è un qualche individuo che rende
disponibile la sua home page pur senza disporre di un dominio
registrato. Il carattere tilde viene utilizzato per convenzione a questo
scopo. Sulle tastiere italiane si ottiene la tilde premendo ALT+126
(su Windows) o, in alternativa, indicando nell'URL la sequenza %7e. Nel
primo caso, 126 è il codice ASCII della tilde espresso nella base
decimale; nel secondo caso è lo stesso numero espresso nella base
esadecimale e nella convenzione della codifica degli URL.
Comunque sia, è pratica comune consentire agli utenti dei sistemi Unix (e quindi anche GNU/Linux) di predisporre la propria home page. Per attivare il meccanismo della tilde in Apache bisogna inserire questa direttiva nella parte globale della configurazione:
UserDir public_html |
il cui significato è il seguente: mappa gli URL della forma
http://localhost.localdomain/~utente
nel pathfile
/home/utente/public_html
. Si possono abilitare anche i CGI
nel solito modo:
ScriptAliasMatch /~(.*)/(.*)\.cgi /home/$1/public_html/$2.cgi |
Il pattern (.*)
esegue il match col nome dell'utente, che viene
sostituito in $1; di conseguenza la stringa (.*)
deve
eseguire il match con tutto ciò che resta, che viene sostituito
in $2. Il risultato di queste due sostituzioni fornisce ad Apache il
pathfile assoluto del CGI nel file system. Quindi, ad esempio, l'URL
http://localhost.localdomain/~tizio/x/y/z.cgi
produce gli assegnamenti $1=tizio
, $2=x/y/z
e quindi in definitiva Apache invoca il CGI
/home/tizio/public_html/x/y/z.cgi
Finalmente, siamo giunti alla conclusione. Sinceramente non credo sia cosa sana tentare tutte le pratiche configuratorie viste fin qua e ridursi solo ora a provare le cose. Più probabile che tu abbia fatto le tue prove un po' alla volta modificando progressivamente il file preconfezionato.
Comunque sia andata, adesso bisogna proprio verificare il funzionamento di SUEXEC. A questo scopo conviene loggarsi come utente, per esempio il sig. Tizio, in modo che tutti i file e le directory create abbiano automaticamente l'identità giusta e facciano contento SUEXEC, che così non si lamenterà. Crea uno script di test come questo:
#!/bin/bash echo "Content-Type: text/plain" echo "" echo "Identita':" id |
/home/tizio/public_html/test.cgi
e rendilo
eseguibile:
$ chmod u=rwx,go= test.cgi |
Eseguilo localmente per vedere se funziona (./test.cgi
),
quindi prova l'URL http://www.tizio.casa/test.cgi
. Se
hai configurato le home page degli utenti puoi provare anche
http://localhost/~tizio/test.cgi
. Sul browser
dovrebbe apparire l'id tizio/users
, prova provata che
il nostro programmino test.cgi
è stato eseguito
con l'identità del suo proprietario.
Ecco quello che vedo io:
$ (echo -ne "GET /test.cgi HTTP/1.0\nHost: www.tizio.casa\n\n" > sleep 2) | telnet localhost 80 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. HTTP/1.1 200 OK Date: Tue, 11 Dec 2001 21:15:53 GMT Server: Apache/1.3.19 (Unix) (Red-Hat/Linux) Connection: close Content-Type: text/plain Identita': uid=506(tizio) gid=100(users) groups=100(users) Connection closed by foreign host. |
La cosa importante sono le due paroline evidenziate in grassetto,
tizio
e user
: è per vederle comparire
che abbiamo fatto tutta questa trafila del SUEXEC. Congratulazioni, obiettivo
centrato!
Se invece, come è probabile, salta fuori qualcos'altro, allora
non resta che ripercorrere tutti i passi della configurazione fatta fin
qui, tenendo sempre aperta una bella finestra con tail -f
dei file di log come abbiamo visto. Alcuni suggerimenti diagnostici:
tizio
.
tizio/users
.User
e
Group
nella sezione VirtualHost
del dominio
www.tizio.casa
.
ScriptAliasMatch
nella sezione
VirtualHost
del dominio www.tizio.casa
.Infine, confronta il tuo file di configurazione con quello che riporto qui più avanti.
Rimane ora solo da disabilitare gli interpreti (ad esempio PERL o PHP, se sono installati) incorporati
in Apache. E' facile, basta commentare tutte le righe di
http.conf
che si riferiscono a questi linguaggi. Potremo
sempre usare il PHP e il PERL nei nostri CGI, ma nelle loro versioni
stand-alone consuete.
La direttiva globale
DirectoryIndex index.html index.htm index.shtml index.cgi |
istruisce il server su quale pagina ritornare quando il client esegue una
GET /
o comunque un'altra directory, senza specificare un
file preciso all'interno di questa directory. In questi casi, Apache tenta
di ritornare i file specificati, nell'ordine. Le prime voci sono quelle
consuete, mentre l'ultima l'ho aggiunta per l'occasione. Per inciso,
se nessuno di questi file esiste, Apache ritorna l'elenco dei file della
directory se è presente la direttiva Option Indexes
,
altrimenti dà un bel "Forbidden".
httpd.conf
in tutta la sua gloria
Giunti alla conclusione di questo lungo cammino, ecco come appare il mio
httpd.conf
. Raccomando, prima di procedere ad uno sbrigativo
copia-e-incolla, di fare una copia del vostro attuale file di configurazione,
e per due buoni motivi: 1) contiene molti utili commenti, qui omessi;
2) funziona.
Alcuni moduli opzionali li ho commentati, ma si possono re-inserire secondo necessità. La loro omissione velocizza parecchio l'avvio, e riduce il consumo di memoria.
Ho lasciato commentate anche alcune funzionalità che hanno valore di commento per le personalizzazioni successive. Anche qui, vedere la documentazione di Apache per il significato delle varie direttive.
####### httpd.conf: INIZIO ServerType standalone ServerRoot "/etc/httpd" LockFile /var/lock/httpd.lock PidFile /var/run/httpd.pid #ScoreBoardFile /var/run/httpd.scoreboard Timeout 300 KeepAlive On MaxKeepAliveRequests 10 KeepAliveTimeout 60 MinSpareServers 2 MaxSpareServers 10 # Pochi processi --> risparmio memoria; riavvii + veloci StartServers 2 MaxClients 10 MaxRequestsPerChild 100 # Carico solo quello che mi serve: #LoadModule vhost_alias_module modules/mod_vhost_alias.so LoadModule env_module modules/mod_env.so LoadModule config_log_module modules/mod_log_config.so #LoadModule agent_log_module modules/mod_log_agent.so LoadModule referer_log_module modules/mod_log_referer.so LoadModule mime_module modules/mod_mime.so #LoadModule negotiation_module modules/mod_negotiation.so #LoadModule status_module modules/mod_status.so #LoadModule info_module modules/mod_info.so #LoadModule includes_module modules/mod_include.so LoadModule autoindex_module modules/mod_autoindex.so LoadModule dir_module modules/mod_dir.so LoadModule cgi_module modules/mod_cgi.so #LoadModule asis_module modules/mod_asis.so #LoadModule imap_module modules/mod_imap.so #LoadModule action_module modules/mod_actions.so LoadModule userdir_module modules/mod_userdir.so LoadModule alias_module modules/mod_alias.so #LoadModule rewrite_module modules/mod_rewrite.so LoadModule access_module modules/mod_access.so LoadModule auth_module modules/mod_auth.so #LoadModule anon_auth_module modules/mod_auth_anon.so #LoadModule db_auth_module modules/mod_auth_db.so #LoadModule digest_module modules/mod_digest.so #LoadModule proxy_module modules/libproxy.so #LoadModule expires_module modules/mod_expires.so #LoadModule headers_module modules/mod_headers.so #LoadModule usertrack_module modules/mod_usertrack.so LoadModule setenvif_module modules/mod_setenvif.so ClearModuleList #AddModule mod_vhost_alias.c AddModule mod_env.c AddModule mod_log_config.c #AddModule mod_log_agent.c AddModule mod_log_referer.c AddModule mod_mime.c #AddModule mod_negotiation.c #AddModule mod_status.c #AddModule mod_info.c #AddModule mod_include.c AddModule mod_autoindex.c AddModule mod_dir.c AddModule mod_cgi.c #AddModule mod_asis.c #AddModule mod_imap.c #AddModule mod_actions.c AddModule mod_userdir.c AddModule mod_alias.c #AddModule mod_rewrite.c AddModule mod_access.c AddModule mod_auth.c #AddModule mod_auth_anon.c #AddModule mod_auth_db.c #AddModule mod_digest.c #AddModule mod_proxy.c #AddModule mod_expires.c #AddModule mod_headers.c #AddModule mod_usertrack.c AddModule mod_so.c AddModule mod_setenvif.c Listen 80 ####### Red Hat 6.2: User nobody Group nobody ####### Red Hat 7.1: #User apache #Group apache ServerAdmin root@localhost # Permessi globali: <Directory /> Options IncludesNOEXEC Indexes SymLinksIfOwnerMatch AllowOverride None </Directory> UserDir public_html # Permessi di base per le dir. degli utenti: <Directory "/home/*/public_html"> AllowOverride All Options Includes Indexes SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory> ScriptAliasMatch /~(.*)/(.*)\.cgi /home/$1/public_html/$2.cgi ScriptAliasMatch /~(.*)/cgi-bin/(.*) /home/$1/public_html/$2 <Directory /home/*/public_html/cgi-bin> AllowOverride All Options ExecCGI </Directory> DirectoryIndex index.html index.htm index.shtml index.cgi _index AccessFileName .htaccess <Files ~ "^\.ht"> Order allow,deny Deny from all </Files> UseCanonicalName On TypesConfig /etc/mime.types DefaultType text/plain HostnameLookups Off ErrorLog /var/log/httpd/error_log LogLevel warn #LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined LogFormat "%h %l %u %t \"%r\" %>s %b" common #LogFormat "%{Referer}i -> %U" referer #LogFormat "%{User-agent}i" agent LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\"" mioformatolog CustomLog /var/log/httpd/access_log mioformatolog ServerSignature On Alias /icons/ "/home/httpd/icons/" <Directory "/home/httpd/icons"> Options Indexes MultiViews AllowOverride None Order allow,deny Allow from all </Directory> IndexOptions FancyIndexing AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip AddIconByType (TXT,/icons/text.gif) text/* AddIconByType (IMG,/icons/image2.gif) image/* AddIconByType (SND,/icons/sound2.gif) audio/* AddIconByType (VID,/icons/movie.gif) video/* AddIcon /icons/binary.gif .bin .exe AddIcon /icons/binhex.gif .hqx AddIcon /icons/tar.gif .tar AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip AddIcon /icons/a.gif .ps .ai .eps AddIcon /icons/layout.gif .html .shtml .htm .pdf AddIcon /icons/text.gif .txt AddIcon /icons/c.gif .c AddIcon /icons/p.gif .pl .py AddIcon /icons/f.gif .for AddIcon /icons/dvi.gif .dvi AddIcon /icons/uuencoded.gif .uu AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl AddIcon /icons/tex.gif .tex AddIcon /icons/bomb.gif core AddIcon /icons/back.gif .. AddIcon /icons/hand.right.gif README AddIcon /icons/folder.gif ^^DIRECTORY^^ AddIcon /icons/blank.gif ^^BLANKICON^^ DefaultIcon /icons/unknown.gif ReadmeName README HeaderName HEADER IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t AddEncoding x-compress Z AddEncoding x-gzip gz tgz AddLanguage en .en AddLanguage fr .fr AddLanguage de .de AddLanguage da .da AddLanguage el .el AddLanguage it .it # v. mod_negotiation #LanguagePriority it en fr de AddType text/html .shtml AddHandler server-parsed .shtml AddHandler imap-file map BrowserMatch "Mozilla/2" nokeepalive BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 BrowserMatch "RealPlayer 4\.0" force-response-1.0 BrowserMatch "Java/1\.0" force-response-1.0 BrowserMatch "JDK/1\.0" force-response-1.0 NameVirtualHost 127.0.0.1:80 <VirtualHost 127.0.0.1:80> ServerName localhost.localdomain DocumentRoot /home/httpd/html ServerAdmin root@casa.lan ErrorLog logs/localhost.localdomain-error_log CustomLog logs/localhost.localdomain-access_log common </VirtualHost> <VirtualHost 127.0.0.1:80> ServerName www.tizio.casa User tizio Group users DocumentRoot /home/tizio/public_html ServerAdmin root@casa.lan ErrorLog logs/www.tizio.casa-error_log CustomLog logs/www.tizio.casa-access_log common ScriptAlias /cgi-bin /home/tizio/public_html/cgi-bin ScriptAliasMatch /(.*)\.cgi /home/tizio/public_html/$1.cgi </VirtualHost> <VirtualHost 127.0.0.1:80> ServerName www.caio.casa User caio Group users DocumentRoot /home/caio/public_html ServerAdmin root@casa.lan ErrorLog logs/www.caio.casa-error_log CustomLog logs/www.caio.casa-access_log common ScriptAlias /cgi-bin /home/caio/public_html/cgi-bin ScriptAliasMatch /(.*)\.cgi /home/caio/public_html/$1.cgi </VirtualHost> #### httpd.conf: FINE!
Abbiamo visto come in ambiente multiutente si possono creare siti WEB per domini virtuali, abbiamo collocato questi siti in spazi isolati del sistema, abbiamo visto come ciascun soggetto possa creare i propri programmi CGI che girano con la propria identità. Ogni utente può creare file e gestire dati nello spazio della propria home directory, senza interferenze con gli altri utenti.
Ho verificato tutti gli esempi presentati qui, ho installato e configurato
il mio computer come descritto, e tutto mi funziona. Tuttavia la materia
è abbastanza complessa, e la configurazione di un programma come
Apache richiede comunque l'attenta lettura del suo manuale per poterne
sfruttare tutte le potenzialità e per risolvere i problemi che
inevitabilmente si possono incontrare. Non mi illudo di essere stato esaustivo,
né tantomeno infallibile. Ecco perché ho già predisposto
una pagina WEB all'indirizzo
http://digilander.iol.it/salsi/erratacorrige
dove ospitare le inevitabili correzioni e
le integrazioni che si dovessero rendere necessarie. Ovviamente, si tratta
di una vile manovra scaramantica.
Nella prossima puntata affronteremo in dettaglio la tecnologia dei CGI, vedremo il famigerato meccanismo dei cookie, e costruiremo i primi programmi CGI di utilità. Per fare questo useremo un linguaggio di programmazione che tutti gli utilizzatori di GNU/Linux sicuramente già conoscono: Bash, la shell più comunemente utilizzata.
Che il grande totem sia con voi.
L'autoreUmberto Salsi <umberto-salsi@libero.it> ha scritto il suo primo programma nel 1981: un potente ciclo FOR stampava su schermo i numeri da 1 a 10000. Folgorato da questo successo, da allora non ha più smesso di seviziare computer nel software e nell'hardware, e di queste pratiche ne ha fatto il suo lavoro e il suo hobby. Nel 1992 scopre Internet e il mondo delle reti telematiche. Nel 1996 incontra GNU/Linux, ed è un'altra infatuazione. |
<- SL: Intro - Copertina - SL: Postfix -> |