Exploring the roots and future of Internet Relay Chat

Le protocole CTCP expliqué : VERSION, TIME, PING, ACTION et DCC

CTCP fait partie de ces protocoles que tout le monde utilise sans le savoir. Chaque fois que vous tapez /me dans un canal, vous envoyez un message CTCP ACTION. Chaque fois qu'un client affiche la version d'un autre utilisateur, c'est une requête CTCP VERSION. Ce mécanisme date des premiers jours d'IRC et reste fondamental en 2023. Voici comment il fonctionne réellement.

Qu'est-ce que CTCP

CTCP signifie Client-To-Client Protocol. Le nom est trompeur : les messages CTCP transitent par le serveur IRC comme n'importe quel PRIVMSG ou NOTICE. La différence, c'est le formatage. Un message CTCP est encadré par un caractère spécial — le délimiteur \x01 (SOH, Start of Heading, code ASCII 1).

Le principe est simple : le serveur IRC ne comprend pas le CTCP et n'a pas besoin de le comprendre. Il transmet le message tel quel. C'est le client destinataire qui détecte le délimiteur \x01, extrait la commande CTCP et la traite localement. C'est du client-à-client, encapsulé dans le protocole IRC standard.

Cette architecture a un avantage et un inconvénient. L'avantage : on peut ajouter des fonctionnalités sans modifier le serveur. L'inconvénient : comme le serveur ne valide rien, les abus sont faciles. On y revient dans la section sécurité.

Format technique d'un message CTCP

Un message CTCP prend cette forme sur le fil :

# Requête CTCP (envoyée via PRIVMSG)
:nick!user@host PRIVMSG cible :\x01COMMANDE paramètres\x01

# Réponse CTCP (envoyée via NOTICE)
:nick!user@host NOTICE cible :\x01COMMANDE réponse\x01

Le \x01 n'est pas visible dans les clients IRC — c'est le caractère de contrôle ASCII 1. En hexadécimal : 0x01. En Python, vous l'écrivez "\x01". En C : '\001'.

La distinction entre PRIVMSG et NOTICE est fondamentale :

# Requête → toujours dans un PRIVMSG
PRIVMSG Marco :\x01VERSION\x01

# Réponse → toujours dans un NOTICE
NOTICE demandeur :\x01VERSION HexChat 2.16.1 / Linux\x01

Pourquoi NOTICE pour la réponse ? Parce que les clients IRC ne doivent jamais répondre automatiquement à un NOTICE (spécification RFC 2812). Cela empêche les boucles infinies : si deux clients répondaient chacun aux réponses de l'autre, vous auriez un ping-pong infini.

Attention : Certains scripts mal écrits répondent aux CTCP via PRIVMSG au lieu de NOTICE. C'est une violation de la spec qui peut déclencher des boucles de flood entre bots. Vérifiez toujours que vos réponses CTCP passent par NOTICE.

Les commandes CTCP standard

VERSION — La plus courante. Le client demandeur envoie \x01VERSION\x01, le client destinataire répond avec le nom et la version de son logiciel.

# Requête
PRIVMSG Marco :\x01VERSION\x01

# Réponse typique
NOTICE demandeur :\x01VERSION WeeChat 4.1.2 (Linux 6.5.0)\x01

# Autre réponse
NOTICE demandeur :\x01VERSION irssi v1.4.4\x01

J'ai longtemps masqué ma réponse VERSION pour des raisons de sécurité — afficher la version exacte de votre client peut donner des informations utiles à un attaquant qui cherche des vulnérabilités connues. La plupart des clients permettent de personnaliser cette réponse.

PING — Mesure la latence entre deux clients. Le demandeur envoie un timestamp, le destinataire le renvoie tel quel. Le demandeur calcule la différence.

# Requête (timestamp Unix)
PRIVMSG Marco :\x01PING 1686234567\x01

# Réponse (renvoie le même timestamp)
NOTICE demandeur :\x01PING 1686234567\x01

# Le client demandeur calcule : now() - 1686234567 = latence

TIME — Demande l'heure locale du client destinataire.

# Requête
PRIVMSG Marco :\x01TIME\x01

# Réponse
NOTICE demandeur :\x01TIME Thu Jun  8 14:23:45 2023\x01

FINGER — Historiquement utilisé pour obtenir des informations sur l'utilisateur (nom réel, temps d'inactivité). Peu de clients répondent encore à cette commande.

SOURCE — Renvoie l'URL où télécharger le client. Presque plus utilisé.

USERINFO — Renvoie une chaîne d'information définie par l'utilisateur. Rarement configuré.

CLIENTINFO — Liste les commandes CTCP supportées par le client.

# Réponse typique
NOTICE demandeur :\x01CLIENTINFO ACTION PING VERSION TIME DCC\x01

ACTION : le cas spécial de /me

Quand vous tapez /me fait quelque chose, votre client envoie :

PRIVMSG #canal :\x01ACTION fait quelque chose\x01

Le serveur transmet ce message comme un PRIVMSG ordinaire. Le client destinataire détecte le délimiteur CTCP, voit la commande ACTION, et affiche le message dans un format spécial — généralement * Marco fait quelque chose au lieu de <Marco> fait quelque chose.

ACTION est le seul message CTCP qui est envoyé à un canal entier de manière routinière. Les autres commandes CTCP sont quasi-exclusivement échangées entre deux utilisateurs en privé.

Un détail que beaucoup ignorent : ACTION peut aussi être envoyé en query (message privé). /me dans une fenêtre de query fonctionne exactement comme dans un canal.

# ACTION dans un canal
PRIVMSG #irc-france :\x01ACTION teste le CTCP\x01

# ACTION en privé
PRIVMSG Marco :\x01ACTION vous salue\x01

DCC : transferts et chat direct

DCC (Direct Client-to-Client) est techniquement initié via une requête CTCP, mais le transfert de données se fait ensuite en connexion TCP directe entre les deux clients, sans passer par le serveur IRC.

Il existe trois types principaux de DCC :

# DCC CHAT — Ouvre une conversation directe
PRIVMSG Marco :\x01DCC CHAT chat 3232235876 5000\x01
#                              ^IP (long)   ^port

# DCC SEND — Envoie un fichier
PRIVMSG Marco :\x01DCC SEND fichier.zip 3232235876 5000 1048576\x01
#                                       ^IP        ^port ^taille

# DCC RESUME — Reprend un transfert interrompu
PRIVMSG expéditeur :\x01DCC RESUME fichier.zip 5000 524288\x01
#                                              ^port ^position

L'adresse IP dans une requête DCC est encodée en format "long" (entier 32 bits). Pour convertir 192.168.1.100 en long :

192 × 16777216 + 168 × 65536 + 1 × 256 + 100 = 3232235876

DCC pose un problème avec le NAT : si l'un des deux clients est derrière un routeur, la connexion directe échoue. Les solutions : le port forwarding manuel, UPnP (si le routeur le supporte), ou DCC passif (reverse DCC) où c'est le destinataire qui initie la connexion TCP.

Conseil : En 2023, si vous avez besoin de transférer un fichier à quelqu'un sur IRC, utilisez un service de partage en ligne et collez le lien. DCC fonctionne encore, mais les problèmes de NAT, de pare-feu et de sécurité le rendent peu pratique. Réservez DCC aux cas où vous contrôlez les deux machines.

Sécurité et abus du CTCP

Le CTCP a été conçu à une époque où la sécurité n'était pas la priorité. Plusieurs vecteurs d'abus existent :

CTCP flood. Un attaquant envoie des centaines de requêtes CTCP VERSION ou PING à une cible. Le client de la victime répond automatiquement à chaque requête, ce qui peut provoquer un flood de NOTICE et un Excess Flood du côté du serveur, déconnectant la victime.

# Exemple de flood CTCP (ce que voit le serveur)
PRIVMSG victime :\x01VERSION\x01
PRIVMSG victime :\x01VERSION\x01
PRIVMSG victime :\x01VERSION\x01
# ... × 200 en quelques secondes

Les contre-mesures côté client :

# HexChat : Settings → Preferences → Flood Protection
# Limiter les réponses CTCP à N par seconde

# WeeChat :
/set irc.ctcp.version ""
# Désactive la réponse VERSION

# Irssi :
/set ctcp_version_reply off

Fuite d'adresse IP via DCC. Une requête DCC contient l'adresse IP de l'expéditeur en clair. Si vous êtes derrière un VPN mais que votre client initie un DCC, votre vraie IP peut fuiter si le client utilise l'IP locale au lieu de l'IP du VPN.

DCC SEND exploit (2003-2004). Une vulnérabilité historique dans mIRC et d'autres clients permettait l'exécution de code via un nom de fichier DCC SEND malformé. Corrigé depuis longtemps, mais c'est un rappel que le parsing CTCP doit être traité comme du code de sécurité, pas comme du parsing anodin.

Côté serveur, les IRCd modernes proposent des protections :

# UnrealIRCd — Limiter le CTCP flood
set {
    anti-flood {
        target-flood {
            channel-privmsg  10:5;  // 10 msgs / 5 sec
            channel-notice    5:5;
            private-privmsg  10:5;
            private-notice    5:5;
        }
    }
}

# InspIRCd — Module m_noctcp
# Bloque les CTCP dans un canal
# Mode canal : /mode #canal +C

Implémenter le CTCP côté client

Si vous écrivez un bot ou un client IRC, voici comment détecter et traiter les messages CTCP en Python :

def parse_ctcp(message):
    """Extrait les commandes CTCP d'un message IRC."""
    ctcp_delim = '\x01'
    if not message.startswith(ctcp_delim):
        return None
    
    # Retirer les délimiteurs
    content = message.strip(ctcp_delim)
    
    # Séparer commande et paramètres
    parts = content.split(' ', 1)
    command = parts[0].upper()
    params = parts[1] if len(parts) > 1 else ''
    
    return {'command': command, 'params': params}

def handle_ctcp(sock, sender, target, message):
    ctcp = parse_ctcp(message)
    if ctcp is None:
        return False
    
    if ctcp['command'] == 'VERSION':
        sock.send(f"NOTICE {sender} :\x01VERSION MonBot 1.0\x01\r\n".encode())
    elif ctcp['command'] == 'PING':
        sock.send(f"NOTICE {sender} :\x01PING {ctcp['params']}\x01\r\n".encode())
    elif ctcp['command'] == 'TIME':
        import datetime
        now = datetime.datetime.now().strftime('%a %b %d %H:%M:%S %Y')
        sock.send(f"NOTICE {sender} :\x01TIME {now}\x01\r\n".encode())
    elif ctcp['command'] == 'ACTION':
        # Traiter comme un message /me
        print(f"* {sender} {ctcp['params']}")
    
    return True

Le point technique à ne pas rater : les réponses CTCP doivent toujours utiliser NOTICE, jamais PRIVMSG. Et implémentez un rate-limiting sur les réponses — deux ou trois par seconde maximum — pour éviter de vous faire déconnecter par le serveur en cas de flood.

Questions fréquentes

Peut-on bloquer toutes les requêtes CTCP ?

Oui. La plupart des clients permettent de désactiver les réponses CTCP individuellement ou globalement. Sur WeeChat : /set irc.ctcp.* "". Côté serveur, le mode utilisateur +T (sur certains IRCd) bloque les CTCP entrants. Sur UnrealIRCd, le mode canal +C bloque les CTCP dans le canal.

CTCP fonctionne-t-il avec IRCv3 ?

Oui, CTCP reste compatible avec les extensions IRCv3. Cependant, certaines fonctionnalités CTCP sont rendues obsolètes par IRCv3 : par exemple, server-time remplace avantageusement CTCP TIME, et les tags de message offrent des métadonnées que CTCP gérait de manière ad hoc. Le CTCP ACTION reste toutefois universel.

DCC est-il encore utilisé en 2023 ?

Très peu. Le NAT généralisé, les pare-feux et les VPN ont rendu DCC impraticable dans la majorité des cas. Les réseaux IRC modernes recommandent des alternatives : liens de partage de fichiers, ou le DCC passif pour les cas spécifiques. Certains réseaux bloquent même les requêtes DCC par défaut pour des raisons de sécurité.