Architektura typu klient serwer: uproszczony klient POP3 Wydział Inżynierii Mechanicznej i Informatyki Instytut Informatyki Teoretycznej i Stosowanej dr inż. Łukasz Szustak
Składniki systemu poczty e-mail POP3 System poczty e-mail POP3 składa się z trzech składników: klienta poczty e-mail POP3, usługi SMTP (Simple Mail Transfer Protocol) oraz usługi POP3 Klient poczty e-mail POP3: Oprogramowanie klienta poczty e-mail POP3 służy do czytania oraz redagowania wiadomości e-mail Klient poczty e-mail POP3 pobiera pocztę e-mail z serwera poczty i dokonuje jej transferu na komputer lokalny, aby użytkownik mógł nią zarządzać Klient poczty e-mail POP3 bazuje na protokole POP3
Składniki systemu poczty e-mail POP3 Usługa SMTP: System transferu poczty e-mail, który kieruje wiadomość e-mail od nadawcy do adresata przy użyciu protokołu SMTP Usługa POP3 używa usługi SMTP jako systemu transferu poczty e-mail Wiadomość e-mail jest redagowana przez użytkownika w oprogramowaniu klienta poczty e-mail POP3 Następnie, gdy użytkownik połączy się z serwerem poczty za pośrednictwem Internetu lub połączenia sieciowego, usługa SMTP zabiera wiadomość e-mail i transferuje ją Internetem na serwer poczty adresata
Składniki systemu poczty e-mail POP3 Usługa POP3: System pobierania poczty e-mail, który pobiera pocztę e-mail z serwera poczty na lokalny komputer użytkownika przy użyciu protokołu POP3 Protokół POP3 kontroluje połączenie między klientem poczty e-mail POP3 użytkownika a serwerem, na którym poczta e-mail jest przechowywana
System poczty e-mail POP3
System poczty e-mail POP3 Krok 1: wiadomość e-mail zostaje wysłana pod adres osoba@przykład.com Krok 2: wiadomość e-mail zostaje pobrana przez usługę SMTP i wysłana przez Internet Krok 3: domena poczty e-mail, przykład.com, zostaje rozpoznana jako serwer poczty w Internecie, mailserver1.przykład.com (mailserver1.przykład.com jest serwerem poczty, na którym jest uruchomiona usługa POP3 i który odbiera przychodzącą pocztę e-mail dla domeny poczty e-mail przykład.com) Krok 4: wiadomość e-mail wysłana pod adres osoba@przykład.com zostaje odebrana przez serwer poczty mailserver1.przykład.com Krok 5: wiadomość została przeniesiona do katalogu magazynu poczty, w którym jest przechowywana poczta e-mail kierowana pod adres osoba@przykład.com
System poczty e-mail POP3 Krok 6: użytkownik someone łączy się z serwerem poczty, na którym jest uruchomiona usługa POP3, aby sprawdzić, czy nie przyszła nowa poczta e-mail: protokół POP3 wysyła poświadczenia uwierzytelnienia użytkownika, czyli hasło i nazwę użytkownika someone, następnie usługa POP3 weryfikuje te poświadczenia, a następnie akceptuje lub odrzuca połączenie Krok 7: jeśli połączenie zostaje zaakceptowane, cała poczta e-mail dla użytkownika someone, która jest przechowywana w magazynie poczty, zostaje pobrana/przeniesiona z serwera poczty na komputer lokalny użytkownika someone
Protokół POP3 Protokół POP3 (Post Office Protocol 3) jest standardowym protokołem pobierania poczty e-mail Protokół POP3 kontroluje połączenie między klientem poczty e-mail POP3 a serwerem, na którym poczta e-mail jest przechowywana Usługa POP3 wykorzystuje protokół POP3 do pobierania poczty e-mail z serwera Protokół POP3 ma trzy stany przetwarzania związane z obsługą połączenia między serwerem poczty a klientem poczty e-mail POP3: stan uwierzytelniania stan transakcji stan aktualizacji
Protokół POP3 W stanie uwierzytelniania klient poczty e-mail POP3, który łączy się z serwerem, musi zostać uwierzytelniony, aby użytkownicy mogli pobrać swoją pocztę e-mail Jeśli nazwa użytkownika i hasło podane przez klienta poczty e-mail są takie same jak te na serwerze, użytkownik zostaje uwierzytelniony i przechodzi do stanu transakcji W przeciwnym wypadku użytkownik otrzymuje komunikat o błędzie i nie wolno mu nawiązać połączenia w celu pobrania poczty e-mail Aby zapobiec uszkodzeniom magazynu poczty po uwierzytelnieniu klienta, usługa POP3 zablokowuje skrzynkę pocztową użytkownika
Protokół POP3 Pobranie nowej poczty e-mail, która została dostarczona do skrzynki pocztowej użytkownika po uwierzytelnieniu użytkownika (i zablokowaniu skrzynki pocztowej) nie jest możliwe, dopóki połączenie nie zostanie zakończone Ponadto w danej chwili ze skrzynką pocztową może być połączony tylko jeden klient; dodatkowe żądania połączenia ze skrzynką pocztową są odrzucane W stanie transakcji klient wysyła polecenia POP3, które serwer odbiera i odpowiada na nie zgodnie z protokołem POP3 Wszystkie odebrane przez serwer żądania klienta niezgodne z protokołem POP3 są ignorowane, a użytkownikowi jest odsyłany komunikat o błędzie
Protokół POP3 Stan aktualizacji zamyka połączenie między klientem a serwerem Jest to ostatnie polecenie wysłane przez klienta Po zamknięciu połączenia magazyn poczty zostaje zaktualizowany, aby uwzględnić zmiany wprowadzone w czasie, gdy użytkownik był połączony z serwerem poczty Gdy na przykład pobieranie wiadomości e-mail przez użytkownika zostanie zakończone pomyślnie, pobrana wiadomość e-mail zostaje zaznaczona do usunięcia, a następnie usunięta z magazynu poczty, o ile klient poczty e-mail użytkownika nie jest skonfigurowany, aby stało się inaczej
Przykład sesji POP3 Na kolejnych slajdach przedstawiona jest przykładowa sesja POP3 z serwerem www.poczta.o2.pl, w której klient kolejno: podaje identyfikator użytkownika (user) podaje hasło (pass) prosi o listę oczekujących wiadomości (stat) kopiuję kolejne wiadomości z serwera (retr) kasuje wiadomość po jej ściągnięciu (dele) kończy sesję (quit) Dokładny opis mechanizmu protokołu POP3 można znaleźć w dokumencie RFC 1939 (http://tools.ietf.org/html/rfc1939)
Przykład sesji POP3 C: Probuje znalezc adres IP maszyny: poczta.o2.pl C: Adres poczta.o2.pl to 193.17.41.99 C: Lacze sie z 193.17.41.99:110 C: Polaczony. S: odppowiedz serwera: +OK POP3 poczta.o2.pl Ready C: Logowanie uzytkownika: pipuspcz@o2.pl S: odppowiedz serwera: +OK C: Uwierzytelnianie uzytkownia: pipuspcz@o2.pl S: odppowiedz serwera: +OK C: Zalogowany: pipuspcz@o2.pl C: Sprawdzanie czy sa nowe wiadomosci S: odppowiedz serwera: +OK 1 1513 C: Nowych wiadomosc: 1. Całkowity rozmiar: 1513 C: pobieranie kolejnych wiadomości: S: odppowiedz serwera: +OK <...widaomości...> C: Kończenie sesji... S: odppowiedz serwera: +OK
Komunikacja połączeniowa (SOCK_STREAM) Gniazda: główna koncepcja komunikacji połączeniowej
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej Pliki nagłówkowe oraz potrzebne makra #include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <string.h> #include <stdio.h> #include <cstdlib> #define USERNAME "pipuspcz@o2.pl" /* nazwa użytkownika POP3 */ #define PASSWORD "pipus1234" /* haslo do skrzynki pocztowej */ #define POPSERV "poczta.o2.pl" /* serwer POP3 */ #define SERVICE "pop-3" /* korzystamy z POP3 */ #define KEEPM /* kasowanie wiadomości */
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej Poniższy fragment kodu tworzy gniazdo do komunikacji strumieniowej w domenie Internetowej: zostaną wykorzystane protokoły: IP oraz TCP int main() { int sd, /* deskryptor gniazda */ ret, /* kod powrotu funkcji */ nrmsg = 0; /* ilość nowych wiadomości w skrzynce POP3 */ long int msgsize; /* łączna wielkość nowych wiadomości */ struct sockaddr_in saddr; /* adres gniazda serwera POP3 */ struct servent *srvent; /* struktura zawierająca numer portu POP3 */ struct hostent *sent; /* z tej struktury odczytamy adres IP serwera */ char msgbuf[1024], /* bufor na wiadomosci */ buf[256]; /* bufor na komendy/odpowiedzi protokołu POP3 */ sd = socket (PF_INET, SOCK_STREAM, 0); if (sd < 0) { std::cerr<<"socket()"<<std::endl; return 1; }
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej Poniżej została użyta pomocnicza funkcja getservbyname(). Zwraca ona wskaźnik do struktury servent, z której później zostanie odczytany numer portu dla usługi SERVICE ("pop-3") korzystającej z protokołu "tcp" srvent = getservbyname (SERVICE, "tcp"); if (!srvent) { std::cerr<<"getservbyname()"<<std::endl; return 1; }
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej Funkcja gethostbyname() zwraca wskaźnik do struktury hostent Na podstawie jedynego argumentu (POPSERV) zostanie sturktura hostnet uzupełniona o potrzebne dane, które umożliwią zamianę domeny serwera POP3 na jego 32-bitowy adres IP Funkcja inet_ntoa() zamieni 32-bitowy adres IP na jego odpowiednik w notacji xxx.xxx.xxx.xxx std::cout<<"probuje znalezc adres IP maszyny: "<<POPSERV<<std::endl; sent = gethostbyname (POPSERV); if (!sent) { std::cerr<<"gethostbyname()"<<std::endl; return 1; } else { in_addr in; memcpy(&in.s_addr, *sent->h_addr_list, sizeof(in.s_addr)); std::cout<<"adres "<<POPSERV<<" to "<<inet_ntoa(in)<<std::endl; }
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej W dalszej części programu zostanie wypełniona funkcja struktura sockaddr_in, która jest niezbędna do nawiązania połączenia connect() saddr.sin_family = sent->h_addrtype; saddr.sin_port = srvent->s_port; memcpy((char *) &saddr.sin_addr, sent->h_addr, sent->h_length); in_addr in; memcpy(&in.s_addr, *sent->h_addr_list, sizeof(in.s_addr)); std::cout<<"lacze sie z "<<inet_ntoa(in)<<":"<<ntohs (srvent->s_port)<<std::endl;
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej Następnie przy pomocy funkcji connect() następuję połączenie ze zdalnym procesem/serwerem Należy zwrócić uwagę na typ argumentów funkcji, która oczekuje m.in. wskaźnika do struktury sockaddr, a zmienna saddr jest wskaźnikiem do struktury sockaddr_in dlatego też należy odpowiednio zmienić typ (ang. cast) zmiennej saddr Warto sprawdzić, czy funkcja connect() nie zwróciła błędu, gdzie najczęstszymi przyczynami błędu są: nieobecność żadnego procesu nasłuchującego na wybranym porcie zdalnego hosta, brak uprawnień do połączenia się ze zdalnym serwisem oraz wyczerpanie limitu czasowego na połączenie ret = connect (sd, (struct sockaddr *) &saddr, sizeof (saddr)); if (ret < 0) { std::cerr<<"connect()"<<std::endl; return 1; } else { std::cout<<"polaczony.\n"; }
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej Kolejne linijki kodu: memset(buf, 0, sizeof (buf)); recv (sd, buf, sizeof (buf), 0); if( chceck (buf) ) return 1; std::cout<<"logowanie uzytkownika: "<<USERNAME<<std::endl; sprintf (buf, "USER %s\r\n", USERNAME); send (sd, buf, strlen (buf), 0); memset(buf, 0, sizeof (buf)); recv (sd, buf, sizeof (buf), 0); if( chceck (buf) ) return 1; std::cout<<"uwierzytelnianie użytkownia: "<<USERNAME<<std::endl; sprintf (buf, "PASS %s\r\n", PASSWORD); send (sd, buf, strlen (buf), 0); memset(buf, 0, sizeof (buf)); recv (sd, buf, sizeof (buf), 0); if( chceck (buf) ) return 1; std::cout<<"zalogowany: "<<USERNAME<<std::endl;
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej W celu wymiany informacji ze zdalnym serwerem POP3 zostały użyte funkcje send() oraz recv() Komunikacja odbywa się zgodnie z protokołem POP3 (RFC 1081) Funkcja check sprawdza, czy serwer zwrócił odpowiedź '+OK' na wysłane przez klienta POP3 polecenie: int chceck (char *b) { if (!strncmp (b, "+OK", 3)) { std::cout<<"odppowiedz serwera: "<<b<<std::endl; return 0; } else { std::cerr<<"blad! Serwer zwrocil odpowiedz: "<<b<<" Koniec...\n"; return 1; } } Jeżeli w którymkolwiek momencie utworzonej sesji, serwer wysłał odpowiedź zawierającą na początku wiadomości inny ciąg znaków niż '+OK' to oznacza błąd sesji/komunikacji serwer-klient W takim przypadku należy zakończyć działanie programu
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej Jeżeli poprzedni fragment kodu zakończył się powodzeniem, oznacza to że użytkownik został zalogowany na serwerze, jak również będzie mógł przystąpić do pobierania poczty W pierwszej kolejności należy sprawdzić czy na serwerze są nowe wiadomości: std::cout<<"sprawdzanie czy sa nowe wiadomosci..."<<std::endl; sprintf (buf, "STAT\r\n"); send (sd, buf, strlen (buf), 0); memset(buf, 0, sizeof (buf)); recv (sd, buf, sizeof (buf), 0); if( chceck (buf) ) return 1; nrmsg = std::atoi(&buf[4]); msgsize = std::atoi(&buf[6]);
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej W pierwszej kolejności zostaje sprawdzone czy dostępne są nowe wiadomości Następnie znajduje się główna pętla odpowiedzialna za pobranie kolejnych wiadomości: if (nrmsg > 0) { std::cout<<"nowych wiadomosc: "<<nrmsg<<". Całkowity rozmiar: "<<msgsize<<std::endl; for (int n = 1; n <= nrmsg; n++) { sprintf (buf, "RETR %i\r\n", n); send (sd, buf, strlen (buf), 0); memset(buf, 0, sizeof (buf)); recv (sd, buf, sizeof (buf), MSG_PEEK); if( chceck (buf) ) return 1;
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej W obrębie pętli while pobierane są kolejne części transmitowanej wiadomości wynik tego działania jest zapisany do bufora msgbuf Dwa warunki if służą do odpowiedniego ustawienia zmiennych str, end oraz msg_end Umożliwia to wycięcie z wiadomości komuników, które nie są jej częścią ('+OK' i sekwencja CRLF.CRLF) Wartość zmiennej msg_end zostaje ustawiona na 1 jeśli buforze msgbuf zawarta jest ostatnia część transmitowanej wiadomości (czyli na końcu msgbuf jest tylko CRLF.CRLF) memset(msgbuf, 0, sizeof (msgbuf)); while (recv (sd, msgbuf, sizeof (msgbuf), 0) > 0) { char *str = msgbuf, *end = msgbuf + strlen (msgbuf); int msg_end = 0; if (!strncmp (msgbuf, "+OK", 3)) str = strchr (msgbuf, '\12') + 1; if (!strncmp (msgbuf + strlen (msgbuf) - 5, "\15\12.\15\12", 5)) { end = strrchr (msgbuf, '\12') - 4; msg_end = 1; }
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej W dalszej części programu wyświetlany jest kawałek odfiltrowanej wiadomości Jeżeli wiadomość została już cała pobrana wychodzimy z pętli (msg_end==1). std::cout<<str; memset(msgbuf, 0, sizeof (msgbuf)); if (msg_end) { std::cout<<std::endl; break; } } std::cout<<"ok\n";
Uproszczony klient usługi POP3 bazujący na komunikacji połączeniowej Ostatni fragment kodu kasuje pobraną wiadomość (jeśli nie jest zdefiniowane makro KEEPM) Następnie przesyłana jest do serwera POP3 komenda 'QUIT', która kończy sesję oraz kończy działanie programu #ifndef KEEPM std::cout<<"usuwam wiadomosc nr."<<n<<"... "<<std::endl; sprintf (buf, "DELE %i\r\n", n); send (sd, buf, strlen (buf), 0); recv (sd, buf, sizeof (buf), 0); chceck (buf); printf ("Wiadomosc usunieta\n"); #endif } } std::cout<<"zakończenie sesji... "; sprintf (buf, "QUIT\r\n"); send (sd, buf, strlen (buf), 0); recv (sd, buf, sizeof (buf), 0); if( chceck (buf) ) return 1; return 0; }