FAQ About Copertina Emacs |
Articoli
Ora che tutti hanno un server Web con cui giocare, magari qualcuno potrà essere tentato di usare il proprio patrimonio di informazioni per metterle a disposizione delle persone più fidate.
È ovvio che, essendo informazioni private, si vuole avere un
minimo di controllo su chi vi accederà.
Questo può essere ottenuto mediante un'opportuna protezione
tramite password.
In questo momento sto usando il server Web Apache 1.1.1, quindi descriverò il modo in cui questo server gestisce gli accessi. Altri server (ad esempio l'NCSA) agiscono in modo molto simile.
Nel caso di Apache è possibile proteggere una directory mediante password creando all'interno di questa un file di nome .htaccess, che contenga queste istruzioni:
AuthType Basic
AuthName mia
AuthUserFile /etc/apache/passwd
AuthGroupFile /etc/apache/group
<Limit GET POST>
require group mio
</Limit>
La keyword AuthType serve per specificare il tipo di
gestione delle password (inutile preoccuparsi dei vari tipi di
autenticazione, dato che al momento è implementata solo la
Basic
:-).
La keyword AuthName specifica il nome
dell'autorizzazione. In pratica, quando vi comparirà la dialog
di richiesta della password, leggerete qualcosa come:
Enter username for mia at xxx:
dove xxx è il nome della vostra macchina.
Le keywords AuthUserFile e AuthGroupFile
servono per definire i nomi dei files degli utenti e dei gruppi.
Esattamente come accade a livello di login al sistema, esiste un file
che associa gli utenti alle proprie password. In questo caso
però l'appartenenza ai gruppi viene gestita mediante un file
in cui si associano gli utenti ai gruppi.
Ho scelto di sistemare il file dei gruppi e quello delle password in
/etc/apache, directory in cui la distribuzione che uso,
la Debian, inserisce i files di configurazione del server.
Il passo successivo è quello di limitare l'accesso alla
directory (e tutte le sue subdirectory) con la keyword
Limit, richiedendo l'appartenenza ad uno specifico
gruppo con la require group.
Ovviamente mio è il nome del gruppo di cui si
deve fare parte per accedere alla lettura delle pagine riservate.
Quando un utente cercherà di accedere ad una pagina protetta,
il sistema richiederà l'immissione di uno username e di una
password.
Ottenuti questi dati, cercherà nel file dei gruppi, per
assicurarsi che l'utente faccia parte del gruppo autorizzato e
verificherà nel file delle password che l'utente sia a
conoscenza della password giusta.
Questo meccanismo ci permette di poter specificare la nostra password di utente una volta per tutte e di poter girare in tutte le directory in cui è permesso l'accesso ad uno dei gruppi di cui facciamo parte, senza dover ripetere al sistema la nostra password. Normalmente questa sarà ricordata dal nostro browser, che la trasmetterà al server su richiesta.
Fin qui tutto bene.
Creare il file dei gruppi è molto semplice: basta elencare i
gruppi e gli utenti che ne fanno parte. Ad esempio:
mio: nando miriam greta
admin: nando
Il file delle password può essere creato con l'utility htpasswd, che, specificato il pathname del file delle password e il nome dell'utente, richiede la sua password, la critta e la salva nel file.
Questa è però un'operazione da eseguire rigorosamente
da riga di comando e loggandosi come root (se ci tenete alla
sicurezza).
Se amate le interfacce grafiche o se l'amministrazione del vostro
sito Web è affidata ad un vostro collega finestrodipendente a cui
non dareste mai la password di root, potete però
crearvi un vostro tool grafico di amministrazione con cui si possa
operare anche senza essere superuser.
Iniziamo creando una form per la modifica delle password:
<html>
<head>
<title>Modifica della password</title>
</head>
<body>
<font size=4>
<center>
Crea un utente o modifica la sua password
</center>
<p>
<!--#include virtual="/cgi-bin/listautenti"-->
<p>
Inserisci il nome dell'utente nel campo <b>Nome utente</b>.
<p>
La sua password dovrà essere inserita nei campi <b>Password</b>
e <b>Verifica</b>. Il contenuto dei due campi dovrà essere uguale,
altrimenti l'utente non sarà creato o la sua password non verrà
modificata.
<p>
Per attivare la procedura di creazione/modifica dell'utente premere il tasto
<b>Crea/Cambia</b>.
<form name="utente" method=POST action="/cgi-bin/chpasswd">
Nome utente: <input size=20 name=utente>
<!--#include virtual="/cgi-bin/listagruppi"-->
<p>
Password: <input type=password size=8 name=passwd> <br>
Verifica: <input type=password size=8 name=passwd2>
<p>
<input type=submit value="Crea/Cambia">
<input type=reset value=Cancella>
<input type="button" value="Torna al Menu" onClick="location='/admin/'">
<br>
</font>
</body>
</html>
Chiamate questa pagina con un nome del tipo chpasswd.shtml. L'estensione shtml è importante, perché identifica le pagine che devono essere preprocessate dal server.
Il server Apache permette l'uso dei SSI o Server Side Include,
che sono delle particolari istruzioni, inserite come commenti HTML
all'interno della pagina, che vengono interpretate dal server, che
esegue il programma specificato e ne riporta l'output nella pagina
che spedisce al browser.
Questa peculiarità permette quindi la costruzione di pagine
dinamiche, il cui contenuto può variare di volta in
volta.
Per attivare l'uso dei SSI bisogna aggiungere nel file di configurazione di Apache srm.conf la direttiva:
AddType text/x-server-parsed-html .shtml
Nella nostra form abbiamo un SSI:
<!--#include virtual="/cgi-bin/listautenti"-->
che lancia un programma Perl che lista tutti gli utenti e i relativi
gruppi presenti in quel momento sul server.
Il programma è molto semplice:
#!/usr/bin/perl
$passwd = "/etc/apache/passwd";
$group = "/etc/apache/group";
print "Content-Type: text/html\r\n\r\n";
open GROUP, $group or die "Non posso aprire il file dei gruppi: $!";
while(<GROUP>){
($gr, $ut) = split /:/;
@utenti = split /\s/, $ut;
foreach $utente (@utenti){
next if $utente eq '';
push @{ $user{$utente} }, $gr;
}
}
close GROUP;
open PASS, $passwd or die "Non posso aprire il file delle password: $!";
print <<EOF;
Gli utenti attualmente abilitati e i gruppi di cui fanno parte sono:
<p>
<ud>
EOF
while(<PASS>){
$utente = (split /:/)[0];
print "<li><font color='#ff4040'>$utente</font>: <font color='#00ee00'>",
join(" ", @{ $user{$utente} }), "</font>\n";
}
close PASS;
print "</ud>\n";
Le formalità a cui deve adempiere un programma SSI sono all'incirca le stesse a cui devono sottostare i programmi che colloquino con il server http tramite il protocollo CGI. La comunicazione deve iniziare con una stringa di identificazione:
Content-Type: text/html
seguita da due CR/LF.
Da questo punto in poi tutto l'output generato dal nostro programma
sarà inserito nel testo della form.
Il loop:
while(<GROUP>){
($gr, $ut) = split /:/;
@utenti = split /\s/, $ut;
foreach $utente (@utenti){
push @{ $user{$utente} }, $gr;
}
}
legge una riga dal file dei gruppi di Apache e la divide all'altezza del carattere ':'. La prima parte è il nome del gruppo, la seconda è una stringa che contiene i nomi di tutti gli utenti appartenenti al gruppo, separati da spazi.
A sua volta l'elenco degli utenti viene diviso e per ognuno degli utenti viene creato un array associativo la cui chiave è il nome dell'utente e il suo valore è l'array dei gruppi di cui l'utente fa parte.
Questo genere di struttura dati è un po' complessa, quindi
conviene spenderci qualche parola. La base è un array di tipo
particolare (array associativo o hash), il cui indice, invece di
essere un numero, può essere una qualsiasi stringa, in questo
caso il nome dell'utente.
Ognuno degli elementi di questo array è a sua volta un array:
la lista dei gruppi a cui l'utente appartiene.
Il nome dell'hash è user, un suo elemento
è indicizzato da $user{$utente}, in cui
$utente contiene un nome. L'elemento in questione è una lista,
a cui appendiamo il nome del gruppo di cui l'utente fa parte, per
mezzo dell'operatore push.
L'operatore push accetta due argomenti: l'array in cui inserire
l'elemento e lo scalare da inserire.
La forma @{ $user{$utente} } viene utilizzata per indicare
che l'elemento dell'hash è un array.
Spero che siate riusciti a seguirmi in questa complessa spiegazione.
In caso vi stiate perdendo d'animo devo ammettere che le strutture
dati complesse sono una delle cose più difficili non solo del
Perl, ma di qualsiasi linguaggio di programmazione.
L'analogo in C avrebbe comportato un'array di struct i cui elementi
sarebbero stati un puntatore e un puntatore ad un array... non molto
più semplice.
Certo esistono modi più semplici di fare le cose, dal punto di vista dei dati, ma si deve scrivere più codice... e io sono pigro.
Proseguendo con l'analisi del programma, arriviamo alla seconda
parte, che legge il file delle password e per ogni utente trovato
ne stampa il nome e l'elenco dei gruppi di cui fa parte, incapsulando
il tutto con dell'HTML.
L'unica riga criptica di questa parte è (spero :-):
$utente = (split /:/)[0];
La cosa può essere spiegata facilmente: l'operatore
split, come la maggior parte degli operatori e funzioni del
Perl, può agire su una variabile di default:
$_.
In questo caso lo split agisce sulla riga corrente appena letta dal
file delle password e la divide all'altezza del carattere
':', generando una lista con il risultato
dell'operazione.
A noi interessa solo il primo elemento della lista, il nome
dell'utente, e non intendiamo toccare la sua password (per il
momento), quindi prendiamo solo l'elemento [0] di
questo array.
Ma non abbiamo ancora terminato con i SSI: ce n'è ancora uno, quello che nelle mie intenzioni deve produrre una serie di checkbox, una per ogni gruppo presente, e permettere quindi di assegnare l'utente creato ad uno o più gruppi:
#!/usr/bin/perl
$group = "/etc/apache/group";
print "Content-Type: text/html\r\n\r\n";
open GROUP, $group or die "Non posso aprire il file dei gruppi: $!";
print "Gruppi: ";
while(<GROUP>){
$gr = (split /:/)[0];
print "$gr: <input type='checkbox' name='gruppo' value='$gr'>\n";
}
close GROUP;
Se siete riusciti a seguire il programmino precedente, questo vi risulterà banale.
Finito di imbellettare la nostra form, abbiamo bisogno di un programma che si preoccupi di modificare il file delle password, gestendo i dati immessi dall'utente.
Questo programma dovrà leggere e soprattutto scrivere file di proprietà di root, in una directory che sulla mia macchina ha delle permission di questo tipo:
drwxr-xr-x 2 root root 1024 Jan 10 19:46 /etc/apache/
Questo rappresenta un problema, se si tiene conto che i programi CGI
vengono eseguiti con le permission dell'utente nobody.
Il nostro programma dovrà essere suid root, per poter essere
eseguito dall'utente proprietario dei dati da modificare.
Quando si parla di script suid, il discorso si fa spinoso e molti
preferiscono dire che "non si deve", ma in casi come questo, a meno
di non scriverci un apposito modulo per Apache, l'unica soluzione
è quella di rilassare un po' il nostro livello di paranoia.
Ci sono due modi di eseguire uno script con le permission di un altro utente: creare un programma C che sia suid e che si preoccupi di invocare il nostro script una volta entrato in esecuzione con le permission giuste (C wrapper) o usare il suidperl, una variante dell'eseguibile del Perl, che permette l'esecuzione suid degli script ed è molto più paranoico di quello che potrebbe essere l'utente normale che si metta a scrivere un C wrapper.
Di solito preferisco questa ultima soluzione, quindi lo script che presento userà il suidperl, ma potete di cambiare la prima riga per puntare al normale perl e scrivervi un C wrapper.
#!/usr/bin/suidperl
use CGI::Request;
use CGI::Carp;
sub errore
{
my($motivo) = shift;
print <<EOF;
<html>
<head>
<title>Modifica non effettuata</title>
</head>
<body bgcolor="#cc0000">
$motivo
<p>
<form name="indietro">
<input type="button" value="Torna indietro" onClick="history.back()">
<input type="button" value="Torna al Menu" onClick="location='/admin/'">
</form>
</body>
</html>
EOF
}
# Legge gli elementi della form
$req = GetRequest();
$user = $req->param('utente');
$passwd = $req->param('passwd');
$passwd2 = $req->param('passwd2');
@gruppi = $req->param('gruppo');
# E` stato immesso il nome dell'utente?
if($user eq ''){
$motivo = <<EOF;
<em>Non</em> è possibile creare o modificare l'utente, perché
non è stato specificato alcun nome.
EOF
errore($motivo);
return;
}
# E` stato immessa la password?
if($passwd eq ''){
$motivo = <<EOF;
La password dell'utente <b>$user</b> <em>non</em> è stata creata o
modificata, perché non è stata specificata alcuna password.
EOF
errore($motivo);
return;
}
# Test della password immessa
if($passwd ne $passwd2){
$motivo = <<EOF;
La password dell'utente <b>$user</b> <em>non</em> è stata creata o
modificata, perché la password specificata e la sua replica sono
diverse.
EOF
errore($motivo);
return;
}
$newfile = "/etc/apache/passwd";
$oldfile = "${newfile}.old";
# Muove il file delle password
unlink $oldfile or
die "Non posso cancellare il file: $!"
if -e $oldfile;
rename $newfile, $oldfile or
die "Non posso rinominare il file: $!"
if -e $newfile;
$newstring = crypt($passwd,'aa');
# Modifica o aggiunge l'utente
open PASSOLD, $oldfile || die "Non posso leggere il file: $!";
open PASSNEW, ">$newfile" || die "Non posso scrivere il file: $!";
$flag = 0;
while (<PASSOLD>) {
if(s/^${user}:.*/${user}:${newstring}/){
$flag=1;
}
print PASSNEW;
}
print PASSNEW "${user}:$newstring\n" if $flag == 0;
close PASSOLD;
close PASSNEW;
# Modifica i gruppi
$newgroup = "/etc/apache/group";
$oldgroup = "${newgroup}.old";
# Muove il file dei gruppi
unlink $oldgroup or
die "Non posso cancellare il file: $!"
if -e $oldgroup;
rename $newgroup, $oldgroup or
die "Non posso rinominare il file: $!"
if -e $newgroup;
open GROLD, $oldgroup || die "Non posso leggere il file: $!";
open GRNEW, ">$newgroup" || die "Non posso scrivere il file: $!";
while (<GROLD>) {
chomp;
($gr, $ut) = split /:/;
@utenti = split /\s/, $ut;
if(grep /$gr/, @gruppi){ # L'utente deve far parte del gruppo
unless(grep /$user/, @utenti){ # Se non ne fa parte
push @utenti, $user;
}
}else{ # L'utente non deve far parte del gruppo
@utenti = grep {!/\b${user}\b/} @utenti;
}
print GRNEW "$gr:", join(" ", @utenti), "\n";
}
close GROLD;
close GRNEW;
# Feedback
print <<EOF;
<html>
<head>
<title>Modifica effettuata</title>
</head>
<body bgcolor="#669900">
La password dell'utente <b>$user</b> è stata creata o modificata
se esistente.
<p>
EOF
# Warnings
print <<EOF if $passwd eq $user;
<p>
<hr>
<b>Attenzione</b>: la password è uguale al logname
dell'utente e la cosa non è considerata ottimale
dal punto di vista della sicurezza.
<p>
<hr>
EOF
print <<EOF if length($passwd) < 4;
<p>
<hr>
<b>Attenzione</b>: la password è composta da meno di quattro
caratteri e la cosa non è considerata ottimale
dal punto di vista della sicurezza.
<p>
<hr>
EOF
print <<EOF if !defined @gruppi;
<p>
<hr>
<b>Attenzione</b>: l'utente <b>$user</b> non fa parte di nessun gruppo.
<p>
<hr>
EOF
# Fine pagina
print <<EOF;
<form name="indietro">
<input type="button" value="Torna indietro" onClick="history.back()">
<input type="button" value="Torna al Menu" onClick="location='/admin/'">
</form>
</body>
</html>
EOF
In questo programmino uso due moduli esterni che fanno parte di una libreria di moduli per la programmazione con il protocollo CGI: la CGI-modules-2.75.tar.gz. Potete procurarvela su un qualsiasi sito aderente al CPAN (Comprehensive Perl Archive Network), ad esempio questo è il sito italiano del network.
Il primo modulo, CGI::Request, gestisce la lettura dell'input contenuto nella form, il secondo, CGI::Carp, serve per redirigere tutti i nostri messaggi di errore generati con l'operatore die nel file di log degli errori del server Apache.
All'inizio del programmino c'è una subroutine che serve a
generare una pagina HTML per mostrare gli eventuali messaggi di
errore generati dal programma.
Perdonatemi, ma questa volta non ho resistito e mi sono scappate alcune
estensioni di Netscape, non propriamente standard. Del resto sembra
che per alcune persone sia un problema dover cliccare il tasto nella
barra degli strumenti di Netscape, così ho inserito un tasto
Torna indietro.
L'altro tasto porta al menu dell'Amministrazione del Web, di cui
questo programmino potrebbe essere solo una delle tante opzioni (ho
messo questo menu nella directory /admin).
Dopo aver letto i valori degli elementi della form, comincio ad
analizzare alcune possibili condizioni di errore (almeno le più
banali).
Se il test risulta positivo, come nel caso:
if($user eq ''){
in cui il problema è quello che l'amministratore ha dimenticato di inserire il nome dell'utente, allora valorizzo una variabile, $motivo, usando la tecnica dell'here document:
$motivo = <<EOF;
<em>Non</em> è possibile creare o modificare l'utente, perché
non è stato specificato alcun nome.
EOF
Il Perl eredita questa sintassi dalle shell Unix, ma la estende,
rendendola applicabile a qualsiasi situazione che implichi un
"input", quindi anche all'assegnazione di una stringa ad una
variabile.
Tutto ciò che compare tra l'assegnazione e la stringa che
abbiamo battezzato come fine dell'here document
(EOF), viene
inserito nella variabile $motivo, andate a capo
comprese.
La variabile viene poi passata alla subroutine che la inserisce in una pagina e la mostra all'utente.
A questo punto inizia l'elaborazione vera e propria: elimino un eventuale file /etc/apache/passwd.old e rinomino l'attuale passwd in passwd.old, poi uso la funzione crypt per generare la nuova password dell'utente.
In questo programmino uso sempre lo stesso seme per la funzione crypt, la stringa 'aa'. Questo può non essere ciò che si vuole, ma lascio la generazione di una stringa di due caratteri casuali come esercizio al lettore.
Apro il vecchio file delle password e lo leggo riga per riga. Prima di stampare la riga letta cerco di modificarla:
if(s/^${user}:.*/${user}:${newstring}/){
$flag=1;
}
uso l'operatore di sostituzione s///. La prima parte è la regular expression che rappresenta la stringa da modificare: deve essere il nome dell'utente immesso nella form ($user), seguito da un carattere ':', seguito da altri caratteri (la password). La seconda parte rappresenta ciò che deve essere sostituito: il nome dell'utente, i due punti e la nuova password.
Se l'operazione di sostituzione è andata a buon fine, se
quindi l'utente immesso dall'amministratore era già elencato
nel file delle password, allora accendo un flag.
Alla fine del ciclo di lettura/scrittura testo il contenuto del flag:
se risulta spento, vuol dire che l'utente è nuovo, non era
presente nel file, quindi lo devo aggiungere.
Passo al file dei gruppi: elimino un
eventuale file /etc/apache/group.old e rinomino
l'attuale group in group.old
Apro in lettura il vecchio file dei gruppi e in scrittura il
nuovo.
Leggo ogni riga (che contiene un gruppo e l'elenco dei suoi
utenti) e se l'utente che l'amministratore sta inserendo dovrà
appartenere al gruppo in esame, viene inserito nell'elenco (se non
è già presente).
L'elaborazione continua così fino alla fine del file.
Non ci resta che avvisare il nostro pubblico della riuscita dell'operazione, non senza dimostrarci saccenti e critici verso le sue scelte.
Come vedete ci vuole poco per rendere user friendly anche un'attività così bistrattata come l'amministrazione di sistema.
Ho risolto questo problema con un pizzico di pigrizia, come al solito, e invece di scrivermi un programma in C sotto X, ho usato direttamente l'interfaccia utente gentilmente messami a disposizione dal mio browser.
Quale può essere la morale nascosta in tutto ciò?
Forse l'epoca dell'object oriented programming sta per lasciare il
passo a quella del document oriented programming?
A rileggerci al prossimo numero del Pluto Journal.
FAQ About Copertina Emacs |