Configurare Apache

Apache è 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.

Per cominciare
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...

Host virtuali basati sull'indirizzo IP
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.

Host virtuali basati sul nome
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.

Cominciamo la configurazione
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.

PASSO 1: Dichiarare i nuovi domini
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.

PASSO 2: predisporre gli spazi WEB
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:

Abbiamo assegnato esplicitamente il nuovo utente al gruppo users: questo devia dallo schema generale della distribuzione Red Hat, che per default crea un nuovo gruppo per ogni utente.
Abbiamo ampliato i diritti di accesso alla home directory dell'utente con 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
UID:
tizio GID:
users others:
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:

L'UID del processo è uguale all'UID del file.
In questo caso si applicano i tre flag dei permessi di utente del file. Se la combinazione dei flag non permette di eseguire l'operazione richiesta dal processo (lettura, scrittura, esecuzione), l'operazione viene negata indipendentemente da tutti gli altri flag di gruppo e others.
Gli UID del processo e del file differiscono, ma i GID sono uguali.
In tal caso si applicano i tre flag dei permessi di gruppo del file. Anche qui, se la combinazione dei flag non permette di eseguire l'operazione richiesta dal processo, l'operazione viene negata indipendentemente da tutti gli altri flag di others.
L'UID e il GID del processo non corrispondono a quelli del file.
In tal caso si applicano i tre flag dei permessi di others.

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 (nobody oppure apache) deve poi essere inserito nel gruppo di ciascun utente. Questa, in estrema sintesi, la ricetta alternativa.

PASSO 3: Configuriamo Apache
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.

PASSO 4: Creiamo le home page
Questa è la parte facile e rilassante: # 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]


...e così via per gli altri dominii.
PASSO 5: Verifica degli host virtuali
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.

Esperimenti interessanti
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).

WEB dinamico e programmi CGI
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:

attraversamento di directory;
elencazione del contenuto delle directory;
creazione e cancellazione di file in una data dir.;
lettura/scrittura/esecuzione dei file;
accesso a un DBMS;
accesso alle altre 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.

SUEXEC - Ovvero del braccio destro del Demonio
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:

Apri il file /usr/sbin/suexec da mc, e usa l'editor binario integrato di Midnight Commander.
Ora dobbiamo cambiare il path dove suexec consente di eseguire CGI col meccanismo del suexec. Per la RH6.2, individua la stringa /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?
Nel file suexec è hard-coded anche il nome dell'utente sotto il quale viene eseguito Apache. Nella RH6.2 è 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


mentre per la RH7.1 cambia solo il nome del gruppo proprietario: # chown root:apache /usr/sbin/suexec
# chmod u=xs,g=x,o= /usr/sbin/suexec


Adesso il suexec è a posto: controlla che l'identità e i permessi del file suexec appaiano così nella RH6.2: # ls -l /usr/sbin/suexec
---s--x--- 1 root nobody 9488 Aug 12 14:19 /usr/sbin/suexec


mentre nella RH7.1 dovrebbero apparire così: # 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!

ScriptAlias - Definire la/le directory dei CGI
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/


Dato che abbiamo già fatto la configurazione degli host virtuali, nelle sezioni dei VirtualHost metteremo qualcosa come <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.

ScriptAliasMatch - Un nome, un CGI
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


Per i VirtualHost aggiungeremo invece: <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.

Home page personali
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

Proviamo il SUEXEC
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


chiamalo /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:

Netscape mostra sempre lo stesso risultato comunque io agisca.
Bisogna premere il bottone Reload del browser mentre si tiene premuto il tasto SHIFT, altrimenti Netscape restituisce quello che ha nella cache.
Il browser risponde "Not Found" e sul log file appare "unable to stat".
Apache ha risolto l'URL nel pathfile, come viene specificato nel log; sfortunatamente, tale file non esiste, oppure i permessi delle directory non permettono ad Apache (che in questa fase ha ancora l'identità standard) di attraversare una o più delle directory del pathfile. Controlla il corretto pathfile. Controlla i permessi della directory home di tizio.
Il browser mostra che lo script ha identità diversa da tizio/users.
Non sta funzionando SUEXEC. Controlla nel log file se all'avvio di Apache compare il messaggio esplicito della attivazione del SUEXEC. Controlla la presenza delle direttive User e Group nella sezione VirtualHost del dominio www.tizio.casa.
Il browser mostra il sorgente dello script.
Controlla la direttiva ScriptAliasMatch nella sezione VirtualHost del dominio www.tizio.casa.
Infine, confronta il tuo file di configurazione con quello che riporto qui più avanti.

Ritocchi finali
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!