TP-3 - Analyse d'applications et les sockets en python

by Joseph Razik, on 2022-01-14

1   Objectif

L'objectif de ce TP est d'étudier le fonctionnement de quelques protocoles utilisés au niveau application (ftp, telnet, pop3, etc) et qui on la particularité de fonctionner avec des commandes textuelles. Pour cela, vous suivrez le cheminement suivant:

  • Parcourir rapidement la RFC pour comprendre le protocole,
  • Faire une capture avec wireshark de ce protocole lors d'une utilisation (consultation d'une page web, envoi/lecture d'un mail, ...)
  • Rejouer « à la main » la connexion d'un client en vous connectant sur le port de l'application à l'aide d'un client telnet. En effet, chacun des protocoles étudiés utilise un langage à base de commandes textuelles définies dans leur document RFC, et sont donc ainsi « contrôlables » à partir d'un client textuel comme telnet.

2   SMTP et POP3

RFC smtp http://www.ietf.org/rfc/rfc0821.txt et pop3 http://www.ietf.org/rfc/rfc1939.txt

Avant de commencer l'analyse, vérifiez le fonctionnement du serveur d'e-mail.

  • Connectez-vous à la machine virtuelle server avec le compte root
  • Vérifiez l'état du service de gestion d'e-mail postfix avec la commande /usr/local/etc/init.d/postfix status
  • Si le résultat n'est pas running, il faut démarrer le service par la commande /usr/local/etc/init.d/postfix start
  • Vérifiez que le service est dorénavant bien actif.

Utilisez la commande suivante sur la machine client pour envoyer un courrier électronique

echo "Hello" | mailx -s Test1 user@local.demo

Quel est le protocole utilisé, expliquez l'échange ?

Lisez le mail avec les commandes suivantes tout en faisant une écoute wireshark

fetchmail -kvp pop3 server

puis mutt

Quel est le protocole utilisé, expliquez l'échange ?

Vous pouvez maintenant envoyer et lire un mail « à la main » en utilisant telnet sur les ports smtp/pop du serveur en rejouant les échanges que vous avez capturés.

  • En vous connectant depuis client avec la commande telnet sur le port 25 de server, envoyez un email.
  • En vous connectant depuis client avec la commande telnet sur le port 110 de server, lisez le mail que vous venez d'envoyer (les commandes utiles du protocole sont USER, PASS, LIST et RETR, cf. la RFC de POP3).

3   FTP

RFC pour FTP http://www.ietf.org/rfc/rfc959.txt .

Lancez une écoute avec wireshark.

Connectez vous en ftp avec la commande cftp depuis la machine client vers la machine virtuelle contenant le serveur ftp (tapez cftp -h ou juste cftp pour connaître ses paramètres d'utilisation).

Explorez les répertoires distants et téléchargez un fichier (il existe également les commandes ftpget et ftpput sur les machines virtuelles).

Combien y-a-t-il de connexion(s) tcp ? Peut-on rejouer l'échange avec telnet ?

4   HTTP

Lancez une écoute avec wireshark pour faire une capture du protocole http.

Connectez-vous sur la machine client et effectuez la commande suivante :

lynx http://www.local.demo

Faites de même avec la consultation du serveur de l'université (http://www.univ-tln.fr).

Idem pour un serveur externe à l'université.

Pourquoi cela fonctionne-t-il ou ne fonctionne-t-il pas ?

Recommencez en positionnant les variables d'environnement http_proxy pour le client. Rappel :

export http_proxy=http://proxy.univ-tln.fr:3128
export https_proxy=http://proxy.univ-tln.fr:3128

5   Les sockets en python - le client

5.1   Le principe

Les sockets sont basées sur une architecture client/serveur: Le serveur décide d'accepter les demandes de connexion sur un port particulier, tandis que le client demande une connexion sur le serveur sur ce même port.

Une fois la connexion établie, les deux programmes peuvent communiquer sur la socket à l'aide de commandes de réception et d'émission.

5.2   Comprendre et créer un client

À partir de l'exemple de code en python3, testez et comprenez le rôle chaque ligne de celui-ci (paramètres, valeurs, etc).

# coding: utf-8

""" Un client http très simple. """

import socket

# Tout d'abord une interrogation du serveur DNS, même si on ne s'en sert pas.

print("Interrogation du DNS: " + socket.gethostbyname("www.univ-tln.fr"))

# 1- Construction d'un objet qui représente la socket
#    vers laquelle le client veut se connecter

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2- ouverture de la connexion vers la socket

s.connect(("www.univ-tln.fr", 80))

# 3- construction du message à envoyer, ici une requête http.

request = "GET / HTTP/1.1\r\n"
request += "Host: www.univ-tln.fr\r\n"
request += "Connection: Close\r\n\r\n"

# 4- envoi sur la socket

s.send(request.encode('UTF-8'))

# 5- lecture de 15 octets sur la socket (augmenter pour lire plus...)

data = s.recv(15)

# 6- convertion de la séquence de 15 octets en une chaîne de caractères
#    et l'afficher.

print(data.decode('utf-8'))

# 7- fermeture de la socket
s.close()

5.3   Client Time

En vous inspirant de l’exemple précédent écrivez deux clients pour le protocole Time (RFC 868), l'un utilisant le protocole TCP et l'autre utilisant le protocole UDP. Un serveur pour ce protocole est en écoute sur la machine sinfo4.univ-tln.fr.

Attention, cet exercice comporte plusieurs difficultés :

  • Ce protocole retourne le temps sur 4 octets. Il faudra donc convertir le tableau de 4 octets lu en un entier long (voir le module struct https://docs.python.org/3/library/struct.html#module-struct).
  • Pour assurer la portabilité de la représentation des nombres entre les différentes architectures, un standard est fixé sur le réseau, il faut donc faire des conversions avant écriture et après lecture sur la socket, soit avec des paramètres du module struct, soit en utilisant des fonctions explicites :
  • socket.ntohl(x) : Convert 32-bit positive integers from network to host byte order. On machines where the host byte order is the same as network byte order, this is a no-op; otherwise, it performs a 4-byte swap operation.
  • socket.htonl(x) : Convert 32-bit positive integers from host to network byte order. On machines where the host byte order is the same as network byte order, this is a no-op; otherwise, it performs a 4-byte swap operation.
  • D'autres fonctions utiles sur les sockets : help(socket) dans l'interprète ou https://docs.python.org/3/library/socket.html
  • Il existe plusieurs habitudes différentes pour indiquer une date dans un système informatique
    • En secondes depuis 1er janvier 1970 à minuit UTC,
    • En secondes depuis le 1er janvier 1900 à minuit UTC, ... (pour information, il y a 2208988800 s entre les deux),
    • On peut utiliser la fonction time.ctime() pour afficher de façon lisible la date (cf. https://docs.python.org/3/library/time.html pour les conversions de temps en python).

5.4   Client FTP

L'échange suivant montre un échange avec le protocole FTP (sur le serveur virtuel de local.demo) pour se connecter et obtenir des informations sur le système.

220 bftpd 4.2 at 192.168.56.101 ready.

USER user

331 Password please.

PASS user

230 User logged in.

SYST

215 UNIX Type: L8

L'idée générale dans cet exercice est d'écrire un programme Python qui utilisera un automate pour obtenir les informations système par ftp : le programme lit sur la socket et, en fonction de ce qu'il lit et de l'état dans lequel il se trouve, envoie le bon message.

On peut résumer ainsi :

  • Lire les lignes à l'infini
  • Si je suis à l'état 0 et que je reçois une chaîne qui commence par 220, j'envoie USER ... et je passe à l'état 1.
  • Si je suis à l'état 1 ...
  • ...
  • Ne pour oublier de sortir et de déconnecter.

Pour faciliter le traitement des chaînes sur les sockets, ce qui arrive très souvent :

  • Il est par exemple possible de lire une socket comme un fichier texte avec for line in s.makefile('r'):
  • Les fonctions de manipulation sur les chaînes sont rappelées ici : chaines. Il existe aussi la fonction booléenne s.startswith("....") avec s une chaîne de caractères.