Introduzione ai sistemi operativi con UNIX
Prec Succ

4. Utenti di UNIX e permessi

UNIX è un sistema non solo multitasking, ma anche multiutente. Il sistema deve permettere agli utenti di lavorare simultaneamente dando l'impressione ad ogni utente di avere un proprio sistema di calcolo. Come abbiamo visto questo viene realizzato dividendo equamente il tempo di CPU e assegnandolo a rotazione ai vari processi degli utenti. Inoltre il sistema deve gestire le richieste sui dispositivi hardware condivisi come ad esempio il disco. Gli utenti inoltre potrebbero voler lavorare indipendentemente tra loro, controllando l'accesso da parte degli altri ai propri dati o avere la necessità di condividere alcuni file di un progetto comune.

Per poter decidere chi può usare una determinata risorsa, ad es. un file su disco, e come può usarla UNIX assegna dei permessi alla risorsa. Lo stesso per i dispositivi come ad es. il lettore di floppy disk che sono accessibili come file speciali memorizzati nella directory /dev.

Ad esempio, se avete il permesso di lettura sul floppy potete leggerne il boot sector di un dischetto formattato col DOS leggendo il dispositivo corrispondente alla prima unità floppy come se fosse un comune file:

$ ls -l /dev/fd0
crw-r-----  18 root  operator    9,   0 Apr 26 18:54 /dev/fd0
$ od -Ad -aN512 /dev/fd0
od: /dev/fd0: Permission denied
od: /dev/fd0: Bad file descriptor
# od -Ad -aN512 /dev/fd0
0000000   eb   <  90   *   j   /   c   {   I   H   C nul stx soh soh nul
0000016  stx  e0 nul   @  vt  f0  ht nul dc2 nul stx nul nul nul nul nul
0000032  nul nul nul nul nul nul   ) stx   G  e1   =  sp  sp  sp  sp  sp
0000048   sp  sp  sp  sp  sp  sp   F   A   T   1   2  sp  sp  sp  so  us
0000064   be   [   |  ac   "  c0   t  vt   V  b4  so  bb bel nul  cd dle
0000080    ^  eb  f0   2  e4  cd syn  cd  em  eb  fe   T   h   i   s  sp
0000096    i   s  sp   n   o   t  sp   a  sp   b   o   o   t   a   b   l
0000112    e  sp   d   i   s   k   .  sp  sp   P   l   e   a   s   e  sp
0000128    i   n   s   e   r   t  sp   a  sp   b   o   o   t   a   b   l
0000144    e  sp   f   l   o   p   p   y  sp   a   n   d  cr  nl   p   r
0000160    e   s   s  sp   a   n   y  sp   k   e   y  sp   t   o  sp   t
0000176    r   y  sp   a   g   a   i   n  sp   .   .   .  sp  cr  nl nul
0000192  nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
*
0000496  nul nul nul nul nul nul nul nul nul nul nul nul nul nul   U  aa
0000512
# chmod a+r /dev/fd0
$ ls -l /dev/fd0
crw-r--r--  18 root  operator    9,   0 Apr 26 18:54 /dev/fd0
$ od -Ad -aN512 /dev/fd0
(dump ora consentito all'utente ordinario)
Per convenzione il prompt "$ " indica un comando dato da un utente ordinario, mentre "# " indica un comando dato dal root. Nell'esempio precedente si suppone che l'utente ordinario non faccia parte del gruppo operator. Per i dettagli riguardanti l'utility di dumping od(1) vedere la manpage rispettiva. Essa serve per convertire i dati binari in un formato umanamente più leggibile. Una linea con il solo * indica che la linea precedente si ripete indentica più volte, fino all'indirizzo indicato all'inizio della prossima linea. Gli indirizzi sono in decimale e si vede che ogni linea rappresenta sedici byte.

Un altro esempio: supponete che l'utente ant sia loggato su una certa macchina al terminale /dev/ttyv0, mentre l'utente joe abbia fatto il login da remoto tramite telnet(1) o ssh(1) e stia usando il pseudo-terminale /dev/ttyp0. Diamo un'occhiata a proprietari e permessi dei rispettivi terminali:

$ ls -l /dev/ttyv0 /dev/ttyp0
crw--w----  1 joe  tty    5,   0 May 20 18:19 /dev/ttyp0
crw-------  1 ant  tty   12,   0 May 20 18:21 /dev/ttyv0

Ora supponete che ant utilizzando uno dei comandi users(1), w(1), who(1) o finger(1), sappia che joe è loggato nel sistema e voglia inviargli un breve messaggio. Visti i permessi di /dev/ttyp0, ant non potrà scrivere sul terminale di joe, a meno che ant non faccia parte del gruppo tty. Se supponiamo che ant faccia parte di questo gruppo tty potrà scrivere sul terminale di joe semplicemente scrivendo sul file speciale corrispondente al dispositivo, con comandi del tipo:

$ echo messaggio >/dev/ttyp0
$ cat file-messaggio >/dev/ttyp0
Se ant non è membro di tty può utilizzare un altro metodo, basato sul comando write(1), il quale è è in grado di operare con i privilegi del gruppo tty (ha il cosiddetto setgid bit, ne riparleremo meglio in seguito):
$ ls -l `which write`
-r-xr-sr-x  1 root  tty  7524 Apr  3 10:55 /usr/bin/write
Prima di poter usare write(1) però ant deve conferire anche al suo terminale il permesso di scrittura per il gruppo tty, cosa che può fare facilmente col comando mesg(1):
$ mesg
is n
$ mesg y
$ mesg
is y
$ ls -l /dev/ttyv0 /dev/ttyp0
crw--w----  1 joe  tty    5,   0 May 20 18:38 /dev/ttyp0
crw--w----  1 ant  tty   12,   0 May 20 18:39 /dev/ttyv0
$ write joe
similmente joe per rispondere userà il comando:
$ write ant
Se joe quando riceve il messaggio era nel mezzo di una sezione di editing, il messaggio verrà scritto sul terminale per consentirgli di leggerlo, e probabilmente si sovrapporrà al testo che stava scrivendo, ma il buffer interno dell'editor non viene intaccato; molti editor a video prevedono un comando per ridisegnare lo schermo dal buffer (ad es. in vi(1) usate ^L in modalità comandi).

Ogni utente di UNIX ha un nome utente alfanumerico scelto breve per tradizione e convenienza, diciamo non più di 8 caratteri, di solito minuscoli, detto anche login name o username. Ogni utente deve avere un nome utente unico: due utenti diversi non possono avere lo stesso username. A questo nome utente è associato un intero positivo o nullo che viene detto user ID o in breve uid. L'uid del superuser, che ha username root, ed è l'amministratore di sistema con privilegi illimitati, è 0. Il sistema dei privilegi non si applica al root perchè naturalmente questo non deve avere nessuna limitazione per poter gestire e configurare il sistema. Questo account deve essere usato solo per l'amministrazione di sistema e con attenzione ed esperienza, perchè un comando errato può produrre effetti disastrosi. Il kernel utilizza l'uid anzichè il nome utente internamente per ragioni di semplicità ed efficienza. Quindi i nomi utenti vengono sempre convertiti nell'uid corrispondente. Un utente è poi anche membro di almeno uno o più gruppi. Ogni gruppo ha un nome alfanumerico e un corrispondente intero detto group ID o in breve gid in cui convertito. Un gruppo ha anche una lista di utenti che vi appartengono. Tra i gruppi di cui un utente è membro uno solo è definito come gruppo principale o primario. Un gruppo senza alcun membro può essere aggiunto nel sistema, ma la cosa non è molto utile, se non in preparazione per aggiungervi in futuro qualche membro. Si vedano passwd(5), group(5) che descrivono il formato dei database degli utenti UNIX in versione testo. Si veda anche l'utility id(1).

Un esempio di una situazione tipica e semplice: un utente ha username joe, uid 1001 ed è membro di un solo gruppo chiamato ancora joe, che ha gid 1001. Il gruppo joe non ha altri membri.

Allo username è anche associata una password ed entrambi vengono usati nella procedura di log in al sistema. UNIX, per ragioni di sicurezza, impone che ogni utente debba avere un account prima di poter utilizzare il sistema. Ogni programma in UNIX viene eseguito da un utente. Ad ogni utente viene concessa la proprietà e pieni permessi su una directory che di solito è posta sotto /home o /usr/home ed ha lo stesso nome dell'utente. Qui l'utente può certamente creare e conservare i propri file o altre sottodirectory, assegnandogli i permessi che ritiene più opportuni. La shell di login imposta la home directory dell'utente come directory corrente iniziale. Si veda a proposito la system call chdir(2).

UNIX permette all'amministratore di stabilire dei limiti sulla quantità di risorse che un certo utente o gruppo di utenti può richiedere, per evitare che un ristretto gruppo di utenti consumi tutte le risorse. Il termine risorse include lo spazio su disco, il tempo di CPU, la memoria RAM, i processi, ecc.

Non tutti gli account sono account di utenti, ossia sono usati da persone. Alcuni account (gruppi e utenti), detti account di sistema, non sono utilizzabili per il login, ma esistono appositamente per farvi girare servizi di sistema o processi del sistema operativo stesso e associarli ai file di quest'ultimi. I servizi infatti non vengono fatti girare coi privilegi di root se non necessario per ragioni di sicurezza.

Un esempio: il web server Apache solitamente ha bisogno di essere avviato come processo di root. Dopo alcune attività iniziali, il processo httpd principale crea dei processi figli che, eseguiti in parallelo dal sistema operativo grazie alla multiprogrammazione, soddisferanno in parallelo le richieste degli utenti. Il processo httpd principale continua a girare come root, ma i processi figli girano coi privilegi di un utente di sistema, di username ad esempio www:

$ ps -axo user,command | grep httpd
root  /usr/local/sbin/httpd
www   /usr/local/sbin/httpd
www   /usr/local/sbin/httpd
www   /usr/local/sbin/httpd
www   /usr/local/sbin/httpd
www   /usr/local/sbin/httpd
www   /usr/local/sbin/httpd
Come abbiamo visto discutendo della system call open(2), i permessi sono memorizzati da UNIX in variabili di tipo mode_t che solitamente corrisponde ad un unsigned short (così è in FreeBSD). Il valore di queste variabili viene spesso espresso con un numero ottale di quattro cifre, una cifra ottale per ogni tre bit.

Ogni bit di questo valore è un flag che indica se un certo permesso è concesso ad una determinata categoria di utenti o meno (rispettivamente col suo valore 1 o 0):

15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00    posizione del bit
 ?  ?  ?  ?  s  g  t  r  w  x  r  w  x  r  w  x    tipo di permesso associato
                         u        g        o       categoria di utente
categoria di utente | significato
--------------------+--------------------------------------------
u                   | utente possessore della risorsa (user)
g                   | gruppo possessore della risorsa (group)
o                   | tutti gli altri utenti (other)

tipo di permesso    | significato
--------------------+--------------------------------------------
r                   | permesso di lettura (read)
w                   | permesso di scrittura (write)
x                   | permesso di esecuzione (execute)
s                   | permesso sid (set-user-ID) all'esecuzione
g                   | permesso gid (set-group-ID) all'esecuzione
t                   | permesso sticky
?                   | permesso non standard
Ad esempio, un valore tipico di permessi per un file non eseguibile è:
15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00    posizione del bit
 0  0  0  0  0  0  0  1  1  0  1  0  0  1  0  0    valore
                      r  w  -  r  -  -  r  -  -    rappresentazione simbolica
                         u        g        o       categoria di utente
In ottale il valore dei permessi si scrive come 0644, nella rappresentazione simbolica usata da programmi quali ls(1) come rw-r--r--. Per i dettagli della rappresentazione simbolica e informazioni sui permessi s,g,t vedere chmod(1) oppure il seguito di questo testo. Si conviene che se un numero inizia con uno 0 non significativo, la base di numerazione è 8, come nel linguaggio C.

Se questo permesso è applicato ad un file, come ad esempio:

$ ls -l index.php
-rw-r--r--  1 ant  ant  4867 Jan  8 18:51 index.php
si può esprimere a parole dicendo che il file index.php può essere letto e scritto dal possessore (il primo ant), puoò essere solo letto dal gruppo del possessore (il secondo ant) e può essere solo letto da tutti gli altri utenti.

Un permesso restrittivo di comune utilizzo per una directory è il 0700 o rwx------:

15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00    posizione del bit
 0  0  0  0  0  0  0  1  1  1  0  0  0  0  0  0    valore
                      r  w  x  -  -  -  -  -  -    rappresentazione simbolica
                         u        g        o       categoria di utente

Quale sarà il significato di lettura, scrittura od esecuzione su una directory? In UNIX una directory è un tipo di file indice che contiene una lista di nomi di file contenuti (intendendo col termine file anche una directory).

Ad esempio una directory pippo che contiene il file pluto e una sottodirectory cuccia/ appare così:

$ ls -F pippo/
cuccia/ pluto
$ od -c pippo/
0000000    t 252  \0  \0  \f  \0 004 001   .  \0  \0  \0  \v 262 002  \0
0000020   \f  \0 004 002   .   .  \0  \0 355 305  \0  \0 020  \0  \b 005
0000040    p   l   u   t   o  \0   X 317 356 305  \0  \0 330 001 004 006
0000060    c   u   c   c   i   a  \0 317  \0  \0  \0  \0  \0  \0  \0  \0
0000100   \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0001000
Il permesso r è quindi il permesso di listare il contenuto della directory che significa semplicemente leggere il file che rappresenta la directory come un qualsiasi altro file. Naturalmente la libreria C mette a disposizioni funzioni più comode per il listing di directory che sono usate ad esempio da ls(1) stesso. Vedere directory(3).

Il permesso x ha un significato leggermente diverso rispetto ai file: permette ad un processo che giri con i permessi dell'utente che lo possiede di rendere quella directory la sua directory corrente ed è un permesso comunque necessario (ma non sufficiente) per accedere ai file contenuti nella directory. Occorre infatti anche che i permessi dei file contenuti consentano l'accesso.

Il permesso w su una directory consente di creare nuovi file e rimuovere file da quella directory indipendentemente dai permessi di questi file, ma da solo non basta per questo, occorre anche il permesso x sulla directory. Se si hanno i permessi -wx su una directory (non si ha il permesso r) per potere eliminare un file occorre indovinarne o conoscerne esattamente il nome.

Notare che non ci deve essere modo semplice per un utente ordinario di riuscire a scavalcare i permessi, altrimenti la sicurezza sarebbe compromessa. Il kernel si prende carico di porre tutti i necessari controlli e le limitazioni. Così ad esempio la chmod(2) cambia i permessi di un file solo se il processo che la invoca gira con i permessi del file in questione oppure con quelli del super-utente. Altro esempio: chown(2) cambia l'utente possessore di un file solo se è il root ad invocarla; inoltre un utente ordinario può cambiare il gruppo possessore solo in uno dei gruppi al quale appartiene.

Può avere senso anche che l'utente proprietario decida di togliersi alcuni permessi su una risorsa (che si comunque può riassegnare se resta sempre lui il proprietario). Ecco un esempio che dimostra come si può utilizzare la autosottrazione del permesso w in congiunzione con un particolare comportamento interattivo di rm(1) per garantirsi contro la cancellazione accidentale di un file importante:

$ umask
0022
$ touch not
$ rm not
$ chmod -w yes 
$ ls -l yes 
-r--r--r--  1 ant  ant  0 May 13 19:24 yes
$ rm yes
override r--r--r--  ant/ant for yes? n

Prec Indice Succ
Elenco delle principali system call I processi