Laboratorul 11 - DNS & E-mail

Lectură laborator

Obiective

În urma parcurgerii acestui laborator, studentul va fi capabil să:

  • diferențieze și utilizeze două protocoale pentru citirea poștei electronice;
  • folosească protocolul pentru trimiterea de mesaje și atașamente prin poșta electronică;
  • scrie un client simplu de e-mail;
  • opereze cu ierarhia spațiilor de nume și să identifice tipurile de domenii și subdomenii;
  • folosească algoritmul de interogare utilizat de DNS;
  • identifice tipurile de resurse pentru diverse domenii și clasele acestora;
  • folosească un set minimal de funcții pentru aflarea informațiilor unui sistem gazdă.

Introducere

Programele se refera rareori la sisteme gazdă, cutii poștale și alte resurse prin adresele lor binare. În loc de numere binare, se utilizează șiruri ASCII, cum ar fi user@cs.pub.ro. Cu toate acestea, rețeaua înțelege numai adrese binare, deci este necesar un mecanism care să convertească șirurile ASCII în adrese de rețea. Protocolul care se ocupă de acest lucru se numește DNS (Domain Name System - sistemul numelor de domenii). Esența DNS-ului constă dintr-o schemă ierarhică de nume de domenii și dintr-un sistem de baze de date distribuite pentru implementarea acestei scheme de nume. Protocolul este definit in RFC-urile 1034 și 1035.

Primul mesaj e-mail a fost transmis in 1971 de un inginer pe nume Ray Tomlinson. Până la acea dată, puteau fi trimise mesaje doar în cadrul aceluiași calculator. Marea îmbunătățire introdusă de Tomlinson a fost posibilitatea de a trimite mesaje între calculatoare diferite din Internet, folosind semnul ’@’ pentru a desemna mașina spre care se trimite mesajul.

Azi se trimit miliarde de mesaje e-mail pe zi, si totuși multe din caracteristicile de atunci ale mesajelor au rămas.

Protocolul DNS

DNS folosește în general protocolul UDP pe portul 53, dar, în cazul răspunsurilor de dimensiuni mai mari sau pentru operații ca transferul de zone, se utilizează și TCP. Mai recent, s-a introdus și DNS over HTTPS (sau DoH, descris în RFC 8484), care presupune realizarea de cereri DNS peste HTTPS din motive de securitate.

Spațiul de nume

DNS organizează numele resurselor într-o ierarhie de domenii. Un domeniu reprezintă o colecție de sisteme gazdă care au unele proprietăți în comun, cum ar fi faptul că toate aparțin unei aceleiași organizații sau faptul că toate sunt situate geografic în același perimetru.

Fiecare domeniu este partiționat în subdomenii și acestea sunt la rândul lor, partiționate, ș.a.m.d. Toate aceste domenii pot fi reprezentate ca un arbore, după cum se poate vedea mai sus. Frunzele arborelui reprezintă domenii care nu au subdomenii, dar care conțin totuși sisteme. Un domeniu frunză poate conține de la un singur sistem gazdă până la mii de sisteme gazdă.

Domeniile de pe primul nivel se împart în două categorii: generice (gTLD-uri) și de țări (ccTLD-uri). Domeniile generice inițiale erau com (comercial), edu (instituții educaționale), gov (guvernul SUA), int (organizații internaționale), mil (forțele armate ale SUA) și org (organizații nonprofit). În ziua de astăzi, restricțiile legate de astfel de domenii sunt mult mai mici, existând astfel peste 1200 de domenii top-level generice. Domeniile de țări includ o intrare pentru fiecare țară, după cum se definește în ISO 3166. Fiecare domeniu este denumit de calea în arbore până la rădăcină. Componentele sunt separate prin punct. Astfel, departamentul de Calculatoare de la UPB poate fi cs.pub.ro în loc de numele în stil UNIX /ro/pub/cs.

Numele de domenii pot fi absolute sau relative. Un nume absolut de domeniu (FQDN - fully qualified domain name) este un nume de domeniu care nu permite nici o ambiguitate cu privire la locația relativă la rădăcina arborelui de nume de domenii. Astfel de nume absolute de domenii se termină cu punct (de exemplu cs.pub.ro.). În contrast, un nume relativ de domeniu este un nume care are sens numai relativ la un anume domeniu DNS (altul decât cel rădăcină).

Numele de domenii nu fac distincție între litere mici și litere mari, edu sau EDU însemnând practic același lucru. Componentele numelor pot avea o lungime de cel mult 64 de caractere, iar întreaga cale de nume nu trebuie să depășească 255 de caractere.

Fiecare domeniu controlează cum sunt alocate domeniile de sub el. De exemplu, Japonia are domeniile ac.jp și co.jp echivalente cu edu și com. Olanda nu face nicio distincție și pune toate organizațiile direct sub nl. Pentru a crea un nou domeniu, se cere permisiunea domeniului în care va fi inclus. De exemplu, dacă un grup PCom de la CS dorește să fie cunoscut ca pcom.cs.pub.ro, acesta are nevoie de permisiunea celui care administrează cs.pub.ro. Similar, o nouă universitate care dorește obținerea unui domeniu va trebui să ceară permisiunea administratorului domeniului edu. În acest mod, sunt evitate conflictele de nume și fiecare domeniu poate ține evidența tuturor subdomeniilor sale. Odată ce un nou domeniu a fost creat și înregistrat, el poate crea subdomenii, fără a cere permisiune de la cineva din partea superioară a arborelui.

Algoritmul de interogare

Conceptele cu care DNS lucrează sunt:

  1. Servere DNS - Stații care rulează programe de tip server de DNS ce conțin informații asupra bazelor de date DNS și despre structura numelor de domenii.
  2. Resolvere DNS - Programe care folosesc cereri DNS pentru interogarea unor servere DNS. Modul în care se derulează procesul de interogare DNS este cel din figura de mai jos.

Înregistrări de resurse

Fiecărui domeniu, fie că este un singur calculator gazdă, fie un domeniu de nivel superior, îi poate fi asociată o mulțime de înregistrări de resurse (resource records sau RR-uri). Pentru un singur sistem gazdă, cea mai obișnuită înregistrare de resursă este chiar adresa IP, dar există multe alte tipuri.

Atunci când procedura resolver trimite un nume de domeniu DNS, ceea ce va primi ca răspuns sunt înregistrările de resurse asociate acelui nume. Astfel, adevărata funcție a DNS este să realizeze corespondența dintre numele de domenii și înregistrări de resurse.

O înregistrare de resursă este un 5-tuplu. Cu toate că, din rațiuni de eficiență, înregistrările de resurse sunt codificate binar, în majoritatea expunerilor ele sunt prezentate ca text ASCII, câte o înregistrare de resurse pe linie. Formatul utilizat este <Nume_domeniu, Timp_de_viață, Tip, Clasă, Valoare>:

  • Câmpul Nume_domeniu precizează domeniul căruia i se aplică această înregistrare. În mod normal, există mai multe înregistrări pentru fiecare domeniu, și fiecare copie a bazei de date păstrează informații despre mai multe domenii. Acest câmp este utilizat cu rol de cheie de căutare primară pentru a satisface cererile. Ordinea înregistrărilor în baza de date nu este semnificativă. Când se face o interogare despre un domeniu, sunt returnate toate înregistrările care se potrivesc cu clasa cerută.
  • Câmpul Timp_de_viață dă o indicație despre cât de stabilă este înregistrarea.
  • Câmpul Tip precizează tipul înregistrării. Cele mai importante tipuri sunt prezentate în tabelul de mai jos.
Tip Semnificație Valoare
SOA Start autoritate Parametri pentru această zonă
A Adresa IPv4 a unui sistem gazdă Întreg pe 32 de biți
AAAA Adresa IPv6 a unui sistem gazdă Întreg pe 128 de biți
MX Schimb de poștă Prioritate, domeniu dispus să accepte poștă electronică
NS Server de nume Numele serverului pentru acest domeniu
CNAME Nume canonic Numele domeniului
PTR Pointer Pseudonim pentru adresă IP
HINFO Descriere sistem gazdă Unitate centrală și sistem de operare în ASCII
TXT Text Text ASCII neinterpretat

O înregistrare SOA furnizează numele sursei primare de informație despre zona serverului de nume, adresa de e-mail a administratorului, un identificator unic si diverși indicatori și contoare de timp.

Cel mai important tip de înregistrare este înregistrarea A (adresă). Ea păstrează adresa IP de 32 de biți a sistemului gazdă. Următoarea ca importanță este înregistrarea MX. Aceasta precizează numele domeniului pregătit să accepte poștă electronică pentru domeniul specificat. Înregistrările specifică numele serverului. Un exemplu de informație ce se poate găsi în baza de date DNS a unui domeniu este următorul:

; Authoritative Information on physics.groucho.edu.
@ IN SOA niels.physics.groucho.edu.
janet.niels.physics.groucho.edu. {
1999090200 ; serial no
360000 ; refresh
3600 ; retry
3600000 ; expire
3600 ; default ttl
};
; Name servers
IN NS niels
IN NS gauss.maths.groucho.edu.
gauss.maths.groucho.edu. IN A 149.76.4.23
;
; Theoretical Physics (subnet 12)
niels IN A 149.76.12.1
IN A 149.76.1.12
name server IN CNAME niels
otto IN A 149.76.12.2
quark IN A 149.76.12.4
down IN A 149.76.12.5
strange IN A 149.76.12.6
...
; Collider Lab. (subnet 14)
boson IN A 149.76.14.1
muon IN A 149.76.14.7
bogon IN A 149.76.14.12
...

Servere de nume

Teoretic, un singur server de nume poate conține întreaga bază de date DNS și poate să răspundă tuturor cererilor. În practică, acest server poate fi atât de încărcat încât să devina de neutilizat. Pentru a evita probleme asociate cu existența unei singure surse de informație, spațiul de nume DNS este împărțit în zone care nu se suprapun. O posibilă astfel de împărțire este cea de mai jos.

Fiecare astfel de zonă conține câte o parte a arborelui, precum și numele serverelor care păstrează informația autorizată despre acea zonă. În mod normal, o zonă va avea un server de nume primar, care preia informația dintr-un fișier de pe discul propriu, și unul sau mai multe servere de nume secundare, care iau informația de la serverul primar. Pentru a îmbunătăți fiabilitatea, unele servere pentru o zonă pot fi plasate chiar în afara zonei.

Plasarea limitelor unei zone este la latitudinea administratorului ei. Această decizie este luată în mare parte pe baza numărului de servere de nume care se doresc a se folosi, și a locației acestora. Atunci când un resolver are o cerere referitoare la un nume de domeniu, el transferă cererea unuia din serverele locale de nume. Dacă domeniul este sub jurisdicția serverului de nume, el va întoarce înregistrări de resurse autorizate. O înregistrare autorizata (authoritative record) este cea care vine de la autoritatea care administrează înregistrarea, și astfel este întotdeauna corectă. Înregistrările autorizate se deosebesc de înregistrările din memoria cache, care pot fi expirate.

Dacă totuși domeniul se află la distanță, iar local nu este disponibilă nici o informație despre el, atunci serverul de nume trimite un mesaj de cerere către serverul de nume de pe primul nivel al domeniului solicitat. De menționat că metoda de interogare este recursivă (recursive query), deoarece fiecare server care nu are informația cerută o caută în altă parte și raportează.

API DNS

gethostbyname() și gethostbyaddr()

Până de curând, pentru a afla un nume pe baza unei adrese IP și o adresă pe baza unui nume, se foloseau funcțiile gethostbyname() și gethostbyaddr(), împreună cu structura hostent. Între timp, acest API pentru DNS a fost scos din uz.

getaddrinfo()
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
 
int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints, struct addrinfo **res);

Funcția getaddrinfo() primește informații despre numele unei gazde și al unui serviciu Internet, și returnează adresa sau adresele corespunzătoare. Parametrul node reprezintă numele simbolic (sub forma unui șir de caractere) al mașinii căreia vrem sa-i aflăm adresa (de exemplu, node poate fi “www.google.com”). Mai poate de asemenea fi reprezentat ca un șir care conține o adresă IPv4 sau IPv6.

Parametrul service specifică portul returnat în output, și poate fi pus pe NULL (caz în care portul din output rămâne neinițializat) sau poate fi dat ca un nume de serviciu (de exemplu, “http”) sau ca o valoare numerică (“80”).

Parametrul hints reprezintă criterii pentru filtrarea adreselor întoarse de apelul funcției getaddrinfo(). Este de tipul struct addrinfo, definit mai jos:

struct addrinfo {
    int              ai_flags;
    int              ai_family;
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};

În cazul în care se dorește filtrarea, se pot completa unul sau mai multe din următoarele câmpuri (restul punându-se pe 0):

  • ai_family - se specifică familia de adrese pentru valorile returnate, putând fi setată ca AF_INET (pentru IPv4), AF_INET6 (pentru IPv6) sau AF_UNSPEC (pentru ambele)
  • ai_socktype - se filtrează după tipul de socket (SOCK_DGRAM sau SOCK_STREAM, de exemplu)
  • ai_protocol - se specifică protocolul setat în adresele returnate de funcția getaddrinfo()
  • ai_flags - se pot seta o serie de flag-uri.

În final, rezultatul este pus în parametrul res, fiind reprezentat ca o listă înlănțuită de structuri de tipul addrinfo, care se parcurge prin intermediul câmpului ai_next. Din câmpul ai_addr al rezultatului, se pot citi informațiile despre adresa și portul stației gazdă căutate (prin cast la struct sockaddr_in, de exemplu).

În caz de succes, funcția întoarce 0, iar în caz de eroare întoarce o valoare negativă, care poate fi interpretată prin intermediul funcției gai_strerror():

const char *gai_strerror(int errcode);

Parametrul res este alocat de către funcția getaddrinfo(), însă el trebuie dezalocat explicit de către utilizator prin intermediul funcției freeaddrinfo():

void freeaddrinfo(struct addrinfo *res);

Pentru a afișa adresa IP (v4 sau v6) corespunzătoare numelui simbolic din parametrul node, se poate utiliza funcția inet_ntop():

#include <arpa/inet.h>
 
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

Primul parametrul specifică familia de protocoale (AF_INET sau AF_INET6), al doilea parametru reprezintă structura de adresă (adică, de exemplu, câmpurile sin_addr sau sin6_addr din structurile sockaddr_in pentru IPv4 sau sockaddr_in6 pentru IPv6), al treilea parametru reprezintă un șir de caractere unde va fi scrisă adresa sub formă de string, iar ultimul parametru reprezintă dimensiunea șirului de caractere în octeți. Valoarea de retur a funcției este un pointer la un șir de caractere identic cu cel din parametrul dst, sau NULL în caz de eroare.

getnameinfo()
#include <sys/socket.h>
#include <netdb.h>
 
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host,
                socklen_t hostlen, char *serv, socklen_t servlen, int flags);

Funcția getnameinfo() realizează operația inversă față de getaddrinfo(). Mai precis, primește o adresă și returnează numele simbolic și serviciul specifice adresei respective. Primii doi parametri reprezintă adresa IP (v4 sau v6) care este căutată. Se trimit structuri specifice protocolului dorit (sockaddr_in sau sockaddr_in6) și dimensiunea lor. Rezultatele apelului sunt puse în șirurile de caractere host și serv, care sunt alocate de către utilizator. Parametrii hostlen și servlen reprezintă dimensiunile celor două șiruri de caractere.

Funcția returnează 0 dacă s-a reușit cererea DNS, sau o valoare negativă interpretată cu gai_strerror() în caz contrar.

Cereri DNS în Linux

În Linux, pentru a obține adresele IP ale unei gazde, putem folosi unul din utilitarele host sau nslookup:

$ host google.com
google.com has address 172.217.20.14
google.com has IPv6 address 2a00:1450:400d:804::200e
google.com mail is handled by 30 alt2.aspmx.l.google.com.
google.com mail is handled by 50 alt4.aspmx.l.google.com.
google.com mail is handled by 20 alt1.aspmx.l.google.com.
google.com mail is handled by 10 aspmx.l.google.com.
google.com mail is handled by 40 alt3.aspmx.l.google.com.
$ nslookup google.com
Server:		192.168.100.1
Address:	192.168.100.1#53

Non-authoritative answer:
Name:	google.com
Address: 172.217.20.14

Pentru aflarea unui nume pe baza unei adrese IP, se pot folosi tot host sau nslookup:

$ host 8.8.8.8
8.8.8.8.in-addr.arpa domain name pointer dns.google.
$ nslookup 8.8.8.8
8.8.8.8.in-addr.arpa	name = dns.google.

Authoritative answers can be found from:
in-addr.arpa	nameserver = a.in-addr-servers.arpa.
in-addr.arpa	nameserver = b.in-addr-servers.arpa.
in-addr.arpa	nameserver = c.in-addr-servers.arpa.
in-addr.arpa	nameserver = e.in-addr-servers.arpa.
in-addr.arpa	nameserver = d.in-addr-servers.arpa.
in-addr.arpa	nameserver = f.in-addr-servers.arpa.

Un exemplu de output tcpdump pentru comanda host este mai jos (unde 53 este portul implicit pentru DNS):

$ sudo tcpdump port 53
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pktap, link-type PKTAP (Apple DLT_PKTAP), capture size 262144 bytes

08:12:17.446368 IP 192.168.100.4.63991 > 192.168.100.1.domain: 31461+ A? google.com. (28)
08:12:17.450412 IP 192.168.100.1.domain > 192.168.100.4.63991: 31461 1/0/0 A 172.217.16.110 (44)
08:12:18.455598 IP 192.168.100.4.58146 > 192.168.100.1.domain: 4039+ AAAA? google.com. (28)
08:12:18.463761 IP 192.168.100.1.domain > 192.168.100.4.58146: 4039 1/0/0 AAAA 2a00:1450:400d:803::200e (56)
08:12:19.467330 IP 192.168.100.4.53016 > 192.168.100.1.domain: 58519+ MX? google.com. (28)
08:12:19.514778 IP 192.168.100.1.domain > 192.168.100.4.53016: 58519 5/0/0 MX alt3.aspmx.l.google.com. 40,
MX alt4.aspmx.l.google.com. 50, MX aspmx.l.google.com. 10, MX alt1.aspmx.l.google.com. 20,
MX alt2.aspmx.l.google.com. 30 (136)

Utilitarul dig

dig (Domain Information Groper) este un utilitar Linux care interoghează servere de nume și afișează rezultatele într-o varietate de forme.

Înainte a vedea cum funcționează dig, este util să observăm formatul unui pachet DNS, prezentat mai jos (formatul fiecărei secțiuni se găsește detaliat în RFC 1035):

+---------------------+
|        Antet        |
+---------------------+
|      Întrebare      | întrebarea pentru serverul de nume
+---------------------+
|       Răspuns       | RR-uri care răspund la întrebare
+---------------------+
|      Autoritate     | RR-uri care indică o autoritate
+---------------------+
|      Adițional      | RR-uri care conțin informație adițională
+---------------------+

Pentru a interoga o singură gazdă, comanda de dig arată în felul următor:

$ dig google.com

Output-ul obținut se mapează pe structura unui răspuns standard DNS, cu mențiunea că unele din RR-uri (de exemplu, cele adiționale sau de autoritate) pot să lipsească. Prima parte a output-ului conține informații despre versiunea de dig folosită și opțiunile alese:

; <<>> DiG 9.11.3-1ubuntu1.7-Ubuntu <<>> google.com
;; global options: +cmd

Mai departe, urmează desfășurarea răspunsului primit de la server-ul DNS, începând cu antetul:

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35678
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 1

În continuare, se afișează partea de întrebare (în exemplul nostru, pentru o adresă IPv4, adică A):

;; QUESTION SECTION:
;google.com.			IN	A

După întrebare, urmează RR-urile de răspuns (în cazul nostru, este unul singur, adică adresa IP a gazdei date):

;; ANSWER SECTION:
google.com.		300	IN	A	172.217.16.142

Mai departe, secțiunea cu RR-uri de autoritate ne spune ce servere DNS pot să ne ofere răspunsuri autoritare la cererile noastre:

;; AUTHORITY SECTION:
.			214056	IN	NS	c.root-servers.net.
.			214056	IN	NS	b.root-servers.net.
.			214056	IN	NS	a.root-servers.net.
.			214056	IN	NS	d.root-servers.net.

În continuare, ar putea urma secțiunea de RR-uri adiționale (care ar putea include, de exemplu, adresele serverelor de nume autoritare din secțiunea precedentă), iar la final ni se oferă statistici despre cerere:

;; Query time: 37 msec
;; SERVER: 172.16.10.254#53(172.16.10.254)
;; WHEN: Thu Apr 09 07:32:54 EEST 2020
;; MSG SIZE  rcvd: 266

Așa cum se poate observa și mai sus, o cerere implicită de dig este realizată cu tipul A (adresă IPv4). Dacă se doresc altfel de cereri, acest lucru se poate specifica la rulare, imediat după numele gazdei. Pe lângă înregistrările propriu-zise, putem folosi și wildcard-ul ANY, care ne interoghează după toate tipurile de RR-uri:

$ dig ANY google.com
[...]
;; ANSWER SECTION:
google.com.		283	IN	AAAA	2a00:1450:400d:805::200e
google.com.		39	IN	SOA	ns1.google.com. dns-admin.google.com. 305440781 900 900 1800 60
google.com.		25	IN	A	172.217.20.14
google.com.		26083	IN	NS	ns3.google.com.
[...]

Dacă nu ne interesează tot output-ul de mai sus și vrem să afișăm doar adresa IP (v4 sau v6), putem folosi opțiunea +short:

$ dig A google.com +short
172.217.20.14

$ dig AAAA google.com +short
2a00:1450:400d:803::200e

Dacă dorim să nu afișăm vreuna din secțiunile de răspuns ale dig, putem alege din opțiunile de mai jos:

  • +nocomments – nu se afișează liniile de comentarii
  • +noauthority – nu se afișează secțiunea de RR-uri autoritare
  • +noadditional – nu se afișează secțiunea de RR-uri adiționale
  • +nostats – nu se afișează secțiunea de statistici
  • +noanswer – nu se afișează secțiunea de răspuns.

Dacă dorim, putem dezactiva afișarea tuturor secțiunilor (cu opțiunea +noall) și apoi să alegem ce vrem să afișăm. Astfel, cele două comenzi de mai jos sunt echivalente, afișând doar secțiunea de răspuns:

$ dig google.com +nocomments +noquestion +noauthority +noadditional +nostats

; <<>> DiG 9.10.6 <<>> google.com +nocomments +noquestion +noauthority +noadditional +nostats
;; global options: +cmd
google.com.		56	IN	A	216.58.214.238

$ dig google.com +noall +answer

; <<>> DiG 9.10.6 <<>> google.com +noall +answer
;; global options: +cmd
google.com.		43	IN	A	216.58.214.23

În mod implicit, dig folosește serverele de nume din fișierul /etc/resolv.conf. Totuși, dacă dorim, putem specifica serverul de nume pe care îl interogăm în felul următor:

$ dig @8.8.8.8 google.com
[...]

$ dig @ns1.google.com google.com
[...]

Dacă se dorește interogarea unui server DNS pentru un număr mai mare de gazde (o interogare de tip bulk), acest lucru se poate face prin adăugarea lor într-un fișier și folosirea opțiunii -f:

$ cat queries.txt 
google.com
facebook.com
twitter.com

$ dig -f queries.txt +noall +answer
google.com.		106	IN	A	172.217.19.110
facebook.com.		43	IN	A	185.60.218.35
twitter.com.		1052	IN	A	104.244.42.193
twitter.com.		1052	IN	A	104.244.42.1

Așa cum s-a menționat și mai sus, sistemul DNS este organizat ierarhic, deci o cerere dig parcurge mai multe servere DNS. Acest lucru se poate observa prin intermediul parametrului +trace:

$ dig google.com +noall +answer +trace

Pentru a realiza o căutare inversă (reverse lookup), se folosește opțiunea -x pentru a obține domeniul și numele asociate cu un IP:

$ dig -x 8.8.8.8 +noall +answer

; <<>> DiG 9.10.6 <<>> -x 8.8.8.8 +noall +answer
;; global options: +cmd
8.8.8.8.in-addr.arpa.	17648	IN	PTR	dns.google.

E-mail

Un mesaj e-mail a fost întotdeauna transmis în format plain-text (text clar). Chiar si prin adăugarea atașamentelor, mesajele de e-mail sunt trimise tot ca mesaje plain-text, prin folosirea unor mecanisme de codificare (uuencode/uudecode, MIME/BASE64).

Un mesaj este format dintr-o secțiune de antete (headers), urmată de o secțiune cu conținutul mesajului. Structura antetelor este descrisă în RFC 822, RFC 1521 și RFC 1806, ele având în general următoarea structură:

  • unul sau mai multe antete Received, care indică ce cale a fost urmată de mesaj de la sursa până la destinație
  • Mime-Version: versiunea MIME (Multipurpose Internet Mail Extensions) folosită, 1.0 in general
  • Content-Type: text/plain pentru mesaje text, multipart/mixed pentru mesaje cu atașamente
  • Subject: subiectul mesajului
  • Date: data și ora când a fost trimis mesajul
  • Message ID: un ID pentru mesaj, folosit pentru identificarea în mod unic a unui mesaj
  • From: numele și adresa de mail a expeditorului
  • To: numele și adresa de mail a destinatarului
  • Cc: carbon copy (alți destinatari)
  • alte antete introduse de clientul de e-mail folosit pentru a trimite mesajul.

Conținutul mesajului este textul propriu-zis, pentru mesajele în text clar fără atașamente. Se poate observa mai jos un exemplu de mesaj:

MIME-Version: 1.0
From: profesor@upb.ro
To: student@upb.ro
Subject: Tema
Content-Type: text/plain

Draga student,

Fa-ti tema!

Cu bine,
Profesorul.

Mesajele cu atașamente pot folosi una din următoarele tehnici pentru codificarea acestora:

  • uuencode - la începuturile e-mail-ului, fișierele care se doreau trimise trebuiau convertite în format text și invers prin folosirea utilitarelor numite uuencode/uudecode; și în ziua de azi, unii clienți de mail adaugă atașamentele la sfârșitul mesajelor, codificându-le cu algoritmul folosit de uuencode
  • MIME/Base64 - această tehnologie este cea recomandată pentru trimiterea de mesaje cu atașamente.

Un mesaj cu atașamente codificate MIME arată în felul următor:

MIME-Version: 1.0
From: Student Studentescu <student@upb.ro>
To: Profesor PC <profesor@upb.ro>
Subject: Re: Tema
Content-Type: multipart/mixed; boundary=abc

--abc
Content-Type: text/plain

Atasez tema.

Cu bine,
Studentul

--abc
Content-Type: text/plain
Content-Disposition: attachment; filename="tema.c"

#include <stdio.h>
int main()
{
    printf("Aceasta este tema mea\n");
    return 0;
}
--abc

Se observă faptul că părțile care compun mesajul sunt separate între ele printr-un șir de caractere separator (boundary string), specificat ca un parametru pentru antetul Content-Type. Fiecare parte poate avea la rândul ei propriile antete, care conțin în general tipul și numele fișierului din secțiunea respectivă. În cazul în care se trimit atașamente binare, acestea sunt codificate folosind schema numită Base64, descrisă în RFC 1521.

Protocoalele SMTP, POP3 și IMAP

În terminologia folosită de sistemele de e-mail, există trei actori. Aceștia pot fi situați pe trei mașini diferite sau pot co-exista pe aceeași gazdă:

  1. Mail User Agent (MUA) - aplicația folosită de utilizator pentru a citi și trimite mesaje e-mail (clientul de e-mail); el nu primește direct mesaje, acesta fiind rolul Mailbox Server-ului
  2. Mailbox Server - serverul care primește și stochează mesajele (server de e-mail)
  3. Mail Transfer Agent (MTA) - aplicația care primește și retrimite mesajele spre un alt MTA sau spre un Mailbox Server (“router” de e-mail).

SMTP

SMTP (Simple Mail Transfer Protocol) este un protocol care se folosește pentru trimiterea mesajelor electronice (de la un client către un server). Acesta se foloseste de portul 25 peste TCP și este descris în RFC 821 și RFC 5321.

Mesajele necesare în SMTP pentru trimiterea unui e-mail sunt următoarele:

1: HELO client.upb.ro
2: MAIL FROM: <profesor@upb.ro>
3: RCPT TO: <student@upb.ro>
4: DATA
5: MIME-Version: 1.0
From: profesor@upb.ro
To: student@upb.ro
Subject: Tema
Content-Type: text/plain

Draga student,
  
Fa-ti tema!
   
Cu bine,
Profesorul.
.
6: QUIT

Se trimite deci întâi o comandă “HELO” cu numele de domeniu sau adresa IP a clientului pentru a iniția sesiunea, apoi o comandă “MAIL FROM” cu adresa sursei, “RCPT TO” pentru destinație, “DATA” pentru date (e-mail-ul în sine) și “QUIT” pentru a se închide sesiunea. Secțiunea de date trebuie neapărat terminată cu secvența de caractere <CR><LF>.<CR><LF> (adică o linie nouă urmată de un punct și apoi de încă o linie nouă).

Găsiți mai jos modul implmentare in C a unui mesaj SMTP.

Click to display ⇲

Click to hide ⇱

void create_mail(char *buffer, char* filename) {

  int size = 500;
  int ret, fd;
  char filebuf[500];
  
  size = write_string(buffer + MAXLEN - size, size, "MIME-Version: 1.0\r\n");
  size = write_string(buffer + MAXLEN - size, size, "From: sender@mail.com\r\n");
  size = write_string(buffer + MAXLEN - size, size, "To: receiver@mail.com\r\n");
  size = write_string(buffer + MAXLEN - size, size, "Subject: Mail\r\n");
  size = write_string(buffer + MAXLEN - size, size, "Content-Type: multipart/mixed; boundary=xxx\r\n");
  size = write_string(buffer + MAXLEN - size, size, "\r\n--xxx\r\n");
  size = write_string(buffer + MAXLEN - size, size, "Content-Type: text/plain\r\n\r\n");
  size = write_string(buffer + MAXLEN - size, size, "This is the body of the e-mail.\r\nBest regards\r\n\r\n");
  size = write_string(buffer + MAXLEN - size, size, "--xxx\r\n");
  size = write_string(buffer + MAXLEN - size, size, "Content-Type: text/plain\r\n");
  size = write_string(buffer + MAXLEN - size, size, "Content-Disposition: attachment; filename=\"");
  size = write_string(buffer + MAXLEN - size, size, filename);
  size = write_string(buffer + MAXLEN - size, size, "\"\r\n\r\n");
  //deschide fisier filename
  //citeste mesajul din fisier
  //inchide fisier
  size = write_string(buffer + MAXLEN - size, size, "\r\n--xxx\r\n.");

}

POP3

POP3 (Post Office Protocol 3) este un protocol utilizat pentru citirea mesajelor electronice (de la un server către un client). Clientul va interoga periodic serverul, va descărca mesajele și le va șterge automat de pe server. Comunicația se realizează folosind portul 110 peste TCP, în felul următor:

1: USERNAME username
2: PASS password
3: LIST
4: RETR 1
5: QUIT

IMAP

IMAP (Internet Message Access Protocol) este un protocol care se folosește pentru citirea mesajelor electronice (de la un server catre un client). Clientul interoghează periodic serverul și poate cere mesaje complete sau doar porțiuni (header, body), și nu va șterge automat mesajele de pe server. Comunicația se realizează prin TCP, folosind portul 143.

1: LOGIN username password
2: LIST "" "*"
3: EXAMINE Inbox
4: FETCH 1 BODY[]
5: LOGOUT

Suportul de laborator

I. DNS

Va oferim aici un cod sursă schelet pentru realizarea unei aplicații in C care utilizează API-ul DNS.

II. Email

Va oferim aici un cod sursă schelet pentru realizarea unui client de email SMTP scris in C. Veți folosi un server SMTP acre rulează local. Acesta poate fi creat folosind un utilitar existent în Python, numit smtpd. Rularea acestui utiliar pe portul 25 se face astfel:

sudo python -m smtpd -n -c DebuggingServer 127.0.0.1:25

Atenție! Pentru a putea rula serverul smtpd pe un port mai mic de 1024, trebuie să aveți drepturi de sudo.

Exerciții

I. DNS

Pornind de la codul disponibil aici, implementați următoatrea cerință:

1. Scrieți un program care să afișeze numele și adresele IP pentru un host. Programul poate primi ca parametru fie numele (caz în care se va afișa adresa), fie adresa IP (caz în care se va afișa numele). Testați-va programul folosind informațiile din tabelul de mai jos. Exemplu de apel:

./dns -n google.com
./dns -a 8.8.8.8

2. Folosind utilitarul dig, realizați următoarele sarcini:

  1. realizați cereri de adresă (A) pentru fiecare gazdă din tabelul de mai jos
  2. pentru serverul de mail din tabelul de mai jos, realizați o cerere de tip MX

Găsiți mai jos un tabel cu o serie de gazde și RR-urile asociate:

Click to display ⇲

Click to hide ⇱

Tip Gazdă Răspuns TTL Prioritate
Pentru single-v4 există o singură adresă IPv4
A single-v4.protocoale.life 127.0.0.1 300
Pentru single-v4 există o singură adresă IPv6
AAAA single-v6.protocoale.life ::1 300
Pentru single se definesc 2 adrese (una IPv4 si una IPv6)
A single.protocoale.life 127.0.0.1 300
AAAA single.protocoale.life ::1 300
Spațiul dorinel.protocoale.life este delegat către un alt server de nume ce rulează la adresa potato.dfilip.xyz
NS dorinel.protocoale.life potato.dfilip.xyz 300
Pentru multi-v4 există 4 adrese IPv4
A multi-v4.protocoale.life 127.1.1.1 300
A multi-v4.protocoale.life 127.2.2.2 300
A multi-v4.protocoale.life 127.3.3.3 300
A multi-v4.protocoale.life 127.4.4.4 300
Pentru multi-v6 există 4 adrese IPv6
AAAA multi-v6.protocoale.life ::1 300
AAAA multi-v6.protocoale.life ::2 300
AAAA multi-v6.protocoale.life ::3 300
AAAA multi-v6.protocoale.life ::4 300
Pentru multi se definesc 8 adrese (4 de IPv4 și 4 de IPv6)
A multi.protocoale.life 127.1.1.1 300
A multi.protocoale.life 127.2.2.2 300
A multi.protocoale.life 127.3.3.3 300
A multi.protocoale.life 127.4.4.4 300
AAAA multi.protocoale.life ::1 300
AAAA multi.protocoale.life ::2 300
AAAA multi.protocoale.life ::3 300
AAAA multi.protocoale.life ::4 300
Adresele pc→pcom→protocoale definesc un șir de nume canonice care are la capăt o adresă IPv4
CNAME pc.protocoale.life pcom.protocoale.life 300
CNAME pcom.protocoale.life protocoale.protocoale.life 300
A protocoale.protocoale.life 127.42.42.42 300
Emailul este deservit de 3 servere SMTP cu priorități diferite
MX protocoale.life alt1.gmail-smtp-in.l.google.com 300 10
MX protocoale.life alt2.gmail-smtp-in.l.google.com 300 20
MX protocoale.life alt3.gmail-smtp-in.l.google.com 300 30
Tip Gazdă Răspuns TTL Prioritate

II. E-mail

Pornind de la codul disponibil aici, implementați următoatrea cerință:

1. Implementați un client SMTP peste TCP prin care să trimiteți către serverul smtpd un e-mail care conține niște text și un fișier dat ca parametru sub forma unui atașament de tip text/plain (API-ul necesar pentru conexiunea TCP cu server-ul este detaliat în laboratorul 7).

Bonus
  1. Folosind instrucțiunile de aici, trimiteți un e-mail către asistent prin intermediul serverului SMTP de la Google.
pc/laboratoare/11.txt · Last modified: 2023/05/07 17:56 by dorinel.filip
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0