Leggere le mail con il POP3 |
A cura di Davide Bianchi | Accedere alle informazioni della propria casella postale (o della casella postale di qualcuno di cui si conosce la password) non e' cosi' complicato. L'utilizzo del protocollo POP3 lo consente in modo molto semplice. Vediamo come. |
Leggere la posta...che passione |
La lettura della posta e' una delle operazioni quotidiane che ogni
utente di Internet esegue senza nemmeno pensarci. I programmi di
accesso ai server di posta elettronica sono distrubuiti e conosciuti
da tutti. Di tanto in tanto pero', si ha la necessita' di svolgere qualche attivita' "strana" o di sviluppare un qualche tools o programmino che consenta l'accesso alla posta elettronica in modo automatico. Supponiamo di avere una applicazione che deve essere "telecomandata" attraverso la rete senza pero' avere una vera interfaccia tramite network: cosa e' piu' semplice di fare in modo che l'applicazione continui a monitorare una casella di posta elettronica e che interpreti opportuni comandi inviati tramite la stessa casella di posta? Le caselle di posta elettronica non costano praticamente nulla, e molti server forniscono l'accesso tramite vari protocolli, uno di questi, forse uno dei piu' usati per leggere la posta in arrivo, e' il POP3.
|
|||||||||
POP3: Post Office Protocol vers. 3 |
Il POP3 e' definito come protocollo di accesso alla posta con il
principio della "casella postale". La posta rimane in "giacenza"
all'interno di uno spazio riservato per l'utente finche' l'utente o
chi ha i necessari privilegi (amministratore), rimuove le mail
giacenti. Il protocollo e' definito dallo standard RFC 1725, i dettagli li potete trovare su http://info.internet.isi.edu/in-notes/rfc/files/rfc1725.txt. Di seguito viene dato un piccolo "riassunto" delle parti salienti del protocollo, quel tanto che basta per utilizzarlo.
|
|||||||||
RFC 1725 in breve |
POP3 e' un protocollo definito per la LETTURA della posta, non ha
comandi particolari che consentono di INVIARE posta. Il Server che
fornisce/implementa tale protocollo e' in ascolto sulla porta
110, che e' riservata a questo protocollo. Quando un client accede alla porta 110, il server invia un "messaggio di benvenuto", solitamente una cosa del tipo: +OK POP3 server ready server definitionLa parte server definition non e' obbligatoria, ma molti server inviano la propria designazione ed altre informazioni. Per esempio, se il server supporta la criptatura delle password possono essere inviate informazioni relative al tipo di crittografia supportato. Solo la parte +OK e' significativa comunque, se esiste questa parte il server e' pronto ad ascoltarci. A questo punto siamo nella fase di "riconoscimento" (AUTORIZATION STATE) dell'utente, dobbiamo dire al server chi siamo prima di poter fare qualche cosa di utile.
Per poterci riconoscere dobbiamo inviare al server due comandi: Da notare che prima della password dobbiamo inviare il nome utente, inoltre il server deve "rispondere" al nostro invio indicando un altro +OK ad intendere che ha ricevuto il nome utente ed e' pronto a controllare la nostra password. La password in questo caso e' inviata in chiaro, cosa che non e' molto bella, per inviare la password in modo crittografato occorre utilizzare il comando UIDL, solo che non tutti i server POP supportano la crittografia, quindi al momento non ce ne occuperemo. Una volta che il server ci ha "riconosciuto", ci inviera' un messaggio del tipo: +OK mailbox username@server readyQuesto ci indica che il server e' pronto ad accettare i nostri comandi, siamo usciti dalla fase di AUTORIZATION e siamo entrati in quella di TRANSACTION, o fase operativa.
In questa fase possiamo inviare una serie di comandi, ad ogni comando
correttamente ricevuto ed eseguito il server rispondera' con un
messaggio di tipo +OK, in caso di problemi il server inviera' un
messaggio -ERR specificando il tipo di errore, sara' nostra cura
controllare la "risposta" del server e gestirla di conseguenza:
|
|||||||||
Una semplice prova |
POP3 e' un protocollo relativamente semplice, possiamo fare un
test del protocollo usando il semplice TELNET: proviamo ad aprire
una sessione di TELNET inviando il nostro server di posta POP e
specificando la porta 110 come accesso. Il server rispondera' con +OK, a questo punto inviamo USER nomeutente, sostituendo a nomeutente il nostro identificativo di utente ovviamente, il server rispondera' con +OK password required for user nomeutente, a questo punto inviamo la nostra password con PASS password, il server rispondera' +OK mailbox user@server ready. A questo punto inviamo STAT, il server rispondera' con +OK 0 0 ad indicare che non ci sono mail disponibili nella nostra casella postale, se ci fossero mail la risposta sarebbe +OK n m octets ad indicare che ci sono 'n' mail che occupano complessivamente 'm' bytes. Qui' viene usato il termine octets ad intendere sequenze di 8 bit (bytes). Per concludere la sessione inviamo QUIT.
|
|||||||||
Un semplice client di posta |
Per utilizzare un server POP ci basta un semplice client di posta
che svolga le operazioni su indicate, e magari recuperi anche i vari
messaggi che sono presenti sul server. Il programmino seguente esegue tutte le operazioni necessarie a leggere le mail presenti su un server pop ed a scaricarle e salvarle in 'n' files di testo (ASCII). Per comodita', i singoli file sono denominati nomeutente1, nomeutente2 etc. etc. Questo 'client' e' ben lungi dall'essere pronto per la vendita come "ammazza-outlook", in particolare non supporta il salvataggio degli allegati. Consideratelo quindi un esercizio di comprensione per il funzionamento del protocollo.
|
|||||||||
Il codice del programma |
Qui' di seguito e' riportato il codice del programma di posta POP.
Il codice e' stato scritto da me tempo addietro, e poi adattato perche'
potesse essere compilato sia sotto Windows sia sotto Linux/Unix,
potrebbero esserci dei problemi sotto alcune versioni di Unix per via
degli #include presenti. Il programma e' stato provato sia con Visual C++ sia con Borland C++, si compila con alcuni Warning, soprattutto relativi al fatto che io verifico il codice di ritorno di alcune funzioni che in Windows sono Unsigned con un valore signed.
/********************************************* POP.C A simple implementation of a client that retrive mail from a POP3 server. Written by Davide Bianchi (davide_bianchi@usa.net) Version 1.00 - September 1999 Based on the RFC 1725 http://info.internet.edu/in-notes/rfc/files/rfc1725.txt Revision: October 1999 added the support for Windows Socket (winsock). This program is freely available and redistributable under the GPL licensing. **********************************************************/ #ifdef _WIN_ #include <stdlib.h> #include <sys/types.h> #include <winsock.h> #else #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #endif #include <string.h> #include <sys/stat.h> #include <stdio.h> #define POP_PORT 110 #define BUFLEN 4096 int sock; char *buffer; /********************************************************** This is required because the idiot Windows uses closesocket() instead of the standard close(). **********************************************************/ #ifdef _WIN_ void close( int sock ) { closesocket( sock ); return; } #endif /*********************************************************** SOME USEFULL FUNCTION ***********************************************************/ /*********************************************************** atoaddr() Convert a string to an Internet address, the address can be sent as an IP address xxx.xxx.xxx.xxx or like an hostname. Return the In_Addr or NULL if something wrong happen ***********************************************************/ struct in_addr *atoaddr( char* address ) { struct hostent *host; static struct in_addr saddr; /* First try it as aaa.bbb.ccc.ddd. */ saddr.s_addr = inet_addr(address); if (saddr.s_addr != -1) { return &saddr; } /* try to locate the IP address from the name */ host = gethostbyname( address ); if (host != NULL) { return (struct in_addr *) *host->h_addr_list; } /** GEEE! Unable to locate the address */ return NULL; } /*********************************************************** Send Send data into the socket, this is used to coop with the fact that the Socket can receive less data than we want to send... If something goes wrong, the function return -1 ************************************************************/ int Send( int sock, char *buffer, int len, int flags ) { int count = 0; int res; while( count < len ) { res = send( sock, buffer + count, len - count, flags ); if( res <= 0 ) { perror( "Error during the Send" ); return -1; } else count += res; } /* return the number of sent data */ return count; } /****************************************************** Receive() Handle the "receive" process trought the socket, when the buffer is received, the last char is terminate with a leading \0. If something goes wrong, the function return -1 else the number of character received is returned. ******************************************************/ int Receive( int sock, char *buffer, int buffsize, int flags ) { char *p; int res = recv( sock, buffer, buffsize, flags ); if( res <= 0 ) { perror("Error during the recv()" ); return -1; } else *( buffer + res ) = '\0'; return res; } /***************************************************** checkreply This function is used to check the result of an operation. The result could be +OK or something diffrent. Only the +OK is considered as a "OK" If OK is received the function return 1, else a 0 is returned. *****************************************************/ int checkreply( char *buffer ) { char *ok = "+OK"; if( memcmp( buffer, ok, 3 ) == 0 ) { return 1; } else { perror( buffer ); return 0; } } /***************************************************** computemail This function is used to check how much mail are in the POP. warning: absolutely no error control.... The function return the number of mail to retrive or 0. *****************************************************/ int computemail( char *buffer ) { int mailnum; char *tok = strtok( buffer, " " ); tok = strtok( NULL, " " ); mailnum = atoi( tok ); return(mailnum); } /***************************************************** checklastblock() check if the buffer contains the last-block-mark for the last block of a single message. The last block is marked with a "." alone in one row. The function return 1 if the marker is found. *****************************************************/ int checklastblock( char *buffer ) { char *tok=strtok( buffer, "\n"); while( tok ) { if( memcmp( tok, ".",1 ) == 0 ) return(1); tok=strtok( NULL, "\n" ); } return(0); } /***************************************************** sendmessage() This simple function is used to send a message to the server and get the corresponding reply. *****************************************************/ int sendmessage( char* message ) { int i; sprintf( buffer, "%s\n\0", message ); i=Send( sock, buffer, strlen( buffer ), 0 ); if( i <= 0 ) { perror("Error during sent"); return(-1); } /* receive ack */ i=Receive( sock, buffer, BUFLEN, 0 ); if( i <= 0 || ! checkreply( buffer ) ) { perror( "Error during receive" ); return(-1); } return(0); } /***************************************************** closeall() This function is used to close everything and remove the allocated memory. *****************************************************/ void closeall() { close(sock); if( buffer != NULL ) free( buffer ); return; } /***************************************************** Main program *****************************************************/ int main(int argc, char *argv[]) { struct sockaddr_in server; struct in_addr *addr; /* used to keep track of status */ int i; /* counter of mails */ int count; int totmail; /* file to write the mail into */ FILE *f; /* Under Windows, you have to call the WSAStartup to open a socket */ #ifdef _WIN_ WSADATA WSAData; WSAStartup(MAKEWORD(1,1), &WSAData); #endif /* check if the user passed arguments... */ if (argc <3 ) { printf( "Usage: %s pop_host username password\n", argv[0] ); return(0); } /* try to locate the server */ addr = atoaddr(argv[1]); if( addr == NULL ) { perror("Unknown host." ); return(1); } /* build the socket */ sock = socket( AF_INET, SOCK_STREAM, 0 ); if (sock < 0) { perror("Socket() failed!\n"); return(1); } /* buffer used to read the information */ buffer = (char*) malloc( BUFLEN ); if( buffer == NULL ) { /* error allocating memory */ perror("Memory error!\n" ); return(1); } /* Set up the server address */ memset((char *) &server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = addr->s_addr; server.sin_port = htons( POP_PORT ); /* connect to the host */ if( connect( sock, (struct sockaddr*) &server, sizeof( server ))) { perror("Error during connect\n" ); closeall(); return(1); } /* receive the ACK from the server, should be "+OK" */ i=Receive( sock, buffer, BUFLEN, 0 ); if( i < 0 || ! checkreply( buffer ) ) { /* error during the receive */ closeall(); return(1); } /* send the Username */ sprintf( buffer, "USER %s", argv[2] ); if( sendmessage( buffer ) ) { closeall(); return(1); } /* send the Password WARNING!!! THIS IS A SERIOUS SECURITY HOLE! The password is sent like PLAIN-TEXT. */ sprintf( buffer, "PASS %s", argv[3] ); if( sendmessage( buffer ) ) { closeall(); return(1); } /* good, send the STAT command to retrive the number of mail */ if( sendmessage( "STAT" ) ) { closeall(); return(1); } /* compute how much mail I have to retrive */ totmail = computemail( buffer ); for( count=1; count <= totmail; count++ ) { /* prepare the filename to save the mail */ sprintf( buffer, "%s%d", argv[2], count ); /* open the file */ f = fopen( buffer, "w+"); if( f==NULL ) { perror("Unable to save the mail\n"); closeall(); return(1); } /* retrive the i-mail... */ sprintf( buffer, "RETR %d\n\0", count ); if( sendmessage( buffer ) ) { closeall(); return(1); } /* get the message */ while( 1 ) { /* read a bunch of data */ Receive( sock, buffer, BUFLEN, 0 ); /* for compatibility, I have to change the 13/10 pairs into a single \n char... */ for( i=0; i < strlen( buffer ); i++ ) { if( buffer[i] == 10 ) { buffer[i-1]=' '; buffer[i]='\n'; } } /* save the line in the file */ fprintf(f, "%s\n", buffer ); /* check if the block is the last for this msg */ if( checklastblock( buffer )) break; } /* close the file */ fclose( f ); /* remove the mail */ sprintf( buffer, "DELE %d\n\0", count ); if( sendmessage( buffer ) ) { closeall(); return(1); } } /* send the QUIT command */ sendmessage( "QUIT" ); closeall(); return 0; } |
|||||||||
Qualche semplice spiegazione |
Il programma e' relativamente semplice, dopo aver aperto il Socket
e connesso la porta 110 (il numero della porta e' definito all'inizio
del codice, il programma tenta la connect con il server, se
riceve un errore il tutto si ferma. In caso di successo, viene letto
il messaggio di benvenuto e questo messaggio viene controllato mediante
la funzione checkreply. Solo la parte +OK viene controllata,
niente altro. A questo punto il programma utilizza sendmessage per inviare in sequenza USER e PASS e farsi riconoscere, dopo ogni comandi il risultato viene verificato all'interno della stessa sendmessage, quando abbiamo superato con successo l'autenticazione, viene inviata una STAT, quindi viene recuperato il numero di mail presenti nella casella. Se questo e' maggiore di zero, il ciclo for...next provvede a richiamare la RETR per ogni messaggio ed a salvare lo stesso messaggio in un file creato per l'occasione. Al termine dello scaricamento, viene inviato un comando DELE che marca il record per la cancellazione. Dopo che sono stati scaricati tutti i record, la connessione viene chiusa inviando un QUIT, questo ha l'effetto di cancellare fisicamente i messaggi dalla mailbox.
|
|||||||||
Compilare il programma |
La compilazione del programma non e' un grosso problema, se state
lavorando sotto Windows tuttavia, dovrete definire la variabile
_WIN_, in modo che le parti relative a Windows vengano compilate
e le #include inserite correttamente. Sotto Borland C++ utilizzate -D_WIN_, sotto Visual C++ usate le proprieta' di compilazione nel menu' Progetto. Sotto Linux/Unix il programma si compila senza problemi usando gcc o cc. Una volta compilato potete provare il tutto semplicemente richiando pop hostname user passwordDove hostname e' il vostro server di posta POP, user e' il vostro nome utente e password la vostra password. L'Hostname puo' essere indicato sia come indirizzo IP che come host.domain. Se tutto e' OK vi troverete una serie di file nella directory contenenti le vostre mail. Il codice completo del programma.
|
L'autore | Davide Bianchi, pomposamente definito Software Engineer dal biglietto da visita, lavora per la Square bv, societa' olandese con sede a Roermond, che si occupa di sviluppo di software con orientamento grafico. Attualmente sta' lavorando in Java (e si diverte un sacco). |
Commenti |
Vuoi mandare un commento su questo articolo ? Scrivi all'autore. |
Contribuire |
Ti senti in grado di scrivere un articolo e vorresti vederlo pubblicato? Leggi come fare |
Copyright |
Il presente sito e' frutto del sudore della mia fronte (e delle mie dita),
se siete interessati a ripubblicare uno degli articoli, documenti o
qualunque altra cosa presente in questo sito per cortesia datemene
comunicazione, cosi' il giorno che faccio delle aggiunte potro'
avvisarvi e magari mandarvi il testo aggiornato.
|
Questo sito non e' ottimizzato per la visione con nessun browser
particolare, ne' per nessuna risoluzione particolare ne' impone
l'uso di font particolari. Siate liberi di vederlo come vi pare
e piace.
Ultimo aggiornamento: 01 Novembre 2000