domenica 21 ottobre 2012

Controllo degli accessi con Raspberri Pi - Acquisizione dati

Una delle condizioni che avevo imposto per sviluppare un prodotto sensato era il supporto del maggior numero possibile di dispositivi di autenticazione.



Inizialmente avevo pensato di lavorare con un lettore badge magnetico con interfaccia seriale o TTL, perché è facile trovare guide per interfacciarne uno con arduino (per esempio questa). Il problema è che quest'approccio è molto poco estendibile, e vincola all'utilizzo di un prodotto preciso.

Esaminando più a fondo il problema, ho notato che la maggior parte dei lettori USB viene fornito in due modalità:
- provvisto di driver proprietari (ed software di sviluppo)
- configurato in emulazione tastiera
La modalità di emulazione tastiera è estremamente interessante perché per il sistema operativo il device appare come una comunissima tastiera, e quindi è possibile usare praticamente qualsiasi device che rispetti lo standard HID senza preoccuparsi di drivers specifici.

Il kernel Linux contiene i drivers HID, e quando una periferica di questo tipo viene collegata crea un device in /dev/input. Se avete un pc desktop con qualsiasi distribuzione Linux, potete usare il comando
ls /dev/input/by-id/
per vedere tutte le periferiche di input collegate (mouse, tastiera, joystick e altre periferiche).
Generalmente i device sono creati con nomi quali event0, event1 e così via, ma nella cartella /dev/input/by-id/ vengono creati dei collegamenti con nomi più comprensibili a un essere umano:

~ $ ls /dev/input/by-id/ -l
totale 0
lrwxrwxrwx 1 root root  9 21 ott 19.57 usb-046d_Gaming_Keyboard_G110-event-if01 -> ../event9
lrwxrwxrwx 1 root root  9 21 ott 19.57 usb-046d_Gaming_Keyboard_G110-event-kbd -> ../event8
lrwxrwxrwx 1 root root  9 21 ott 19.57 usb-LOGITECH_G110_G-keys-event-kbd -> ../event7
lrwxrwxrwx 1 root root 10 21 ott 19.57 usb-QCM_Laptop_Integrated_Webcam_HD-event-if00 -> ../event12
lrwxrwxrwx 1 root root 10 21 ott 19.57 usb-Saitek_Cyborg_R.A.T.5_Mouse-event-mouse -> ../event10
lrwxrwxrwx 1 root root  9 21 ott 19.57 usb-Saitek_Cyborg_R.A.T.5_Mouse-mouse -> ../mouse0
 Ora qualcuno di voi potrebbe pensare: "Ora che abbiamo il device file, per acquisire i dati non resta che fare cat /dev/input/eventX!" e, in effetti, era venuto in mente pure a me.
Tuttavia questo metodo è valido solo per testare se davvero state leggendo dei dati dal dispositivo, poiché dal devices vengono lette una serie di strutture dati che rappresentano tutte le informazioni relative all'evento:
- timestamp
- codice dell'evento
- tipo dell'evento
- valore

Cercando più a fondo, ho trovato questa guida che spiega le basi per interagire con il sottosistema di input di Linux. Io non capisco assolutamente niente di C, ma con un po' di ingegno e molto copia-incolla sono riuscito ad estrarre i dati che cercavo.

A questo punto, rimaneva un'ultima questione da esaminare: essendo il lettore di badge a tutti gli effetti trattato come una tastiera, quando viene strisciato un badge il suo valore viene inviato all'applicazione aperta. Se avete il blocco note aperto, vi apparirà il contenuto della banda magnetica.
Essendo CitofonoWeb un'applicazione headless questo non era un grave problema, la sequenza di tasti sarebbe stata inviata alla tty1 in attesa di username e password, che ovviamente li avrebbe rifiutati.
Tuttavia ignorare questo comportava due conseguenze fastidiose:
- milioni di accessi falliti in /var/log/auth.log
- impossibilità di fare manutenzione con una normale tastiera mantenendo il servizio attivo

Ho quindi cercato ancora più a fondo e ho scoperto l'opzione EVIOCGRAB: in breve, permette di acquisire dati dal dispositivo in modalità esclusiva, senza che gli eventi letti si propaghino oltre.

A questo punto, mettendo assieme tutte queste conoscenze sono riuscito a produrre questo:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <dirent.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/select.h> #include <sys/time.h> #include <termios.h> #include <signal.h> #include <linux/input.h> #define EV_PRESSED 1 #define EV_RELEASED 0 #define EV_REPEAT 2 void handler (int sig) { fprintf (stderr,"exiting...(%d)\n", sig); exit (0); } void perror_exit (char *error) { perror (error); handler (9); } int main (int argc, char *argv[]) { setbuf(stdout, NULL); struct input_event event; int fd, rd, value, size = sizeof (struct input_event); char name[256] = "Unknown"; char *device = NULL; //Setup check if (argv[1] == NULL){ fprintf(stderr,"Please specify (on the command line) the path to the dev event interface device\n"); exit (0); } if (argc > 1) device = argv[1]; //Open Device if ((fd = open (device, O_RDONLY)) == -1) fprintf (stderr,"%s is not a vaild device.\n", device); //Print Device Name ioctl (fd, EVIOCGNAME (sizeof (name)), name); fprintf (stderr,"Reading From : %s (%s)\n", device, name); //Uso esclusivo ioctl (fd, EVIOCGRAB, 1); while (1){ /* how many bytes were read */ size_t rb; /* the events (up to 64 at once) */ struct input_event ev[64]; rb=read(fd,ev,sizeof(struct input_event)*64); if (rb < (int) sizeof(struct input_event)) { perror("evtest: short read"); exit (1); } int yalv; for (yalv = 0; yalv < (int) (rb / sizeof(struct input_event)); yalv++) { if (EV_KEY == ev[yalv].type){ //printf("%ld.%06ld ", ev[yalv].time.tv_sec, ev[yalv].time.tv_usec); //printf("type %d code %d value %d\n", ev[yalv].type, ev[yalv].code, ev[yalv].value); //Stampa solo al keydown if (ev[yalv].value==0) printf("%d\n", ev[yalv].code); } } } close(fd); return 0; }

Lo stesso programma potete trovarlo su GitHub all'interno della pagina del progetto CitofonoWeb.
Domani (tempo permettendo) spiegherò più approfonditamente come funziona questo programma C, e come viene usato dal demone di acquisizione dati.

1 commento:

  1. bravissimo! Interessantissimo!
    Potresti indicarmi marca e modello del lettore badge che hai utilizzato?
    ciao

    RispondiElimina