Bloqueo de direcciones XMPP con XEP-0191: Blocking Command

En Cleaning spurious presence subscription requests in Isode XMPP server y Código para limpiar peticiones de suscripción de presencia espurias en el servidor XMPP Isode describo una situación de spam aburrida pero insistente sobre mi dirección XMPP. Una vez solucionado el problema del bug del servidor Isode y la limpieza de las peticiones de suscripción de presencia espurias acumuladas, aún queda el problema del spam futuro.

La X en XMPP viene de eXtensible: Además del protocolo XMPP básico, se han definido y estandarizado numerosos XMPP Extension Protocols que amplían XMPP con nuevas funcionalidades. A la hora de bloquear el spam, nos interesa XEP-0191: Blocking Command. Recomiendo leer el documento. Es claro y fácil de entender.

Modifiqué lo mínimo el código Python que describo en Código para limpiar peticiones de suscripción de presencia espurias en el servidor XMPP Isode para bloquear direcciones XMPP:

xmpp-20190522.py (Código fuente)

#!/usr/bin/env python3

# This code is in the Public Domain.
# You can do with it whatever you want.
#
# 2018-2019 jcea@jcea.es - https://www.jcea.es/ - https://blog.jcea.es/


import socket
import ssl
import base64
import sys
import xml.dom.minidom

import dns.resolver

jid = 'TU JID'
clave = 'TU CLAVE'

jids = sys.argv[1:]

usuario, dominio = jid.split('@')
dns = dns.resolver.query(f'_xmpp-client._tcp.{dominio}', 'SRV')
srv = min(dns.response.answer[0].items, key=lambda x: x.priority)

s = socket.socket()
s.connect((srv.target.to_text(), srv.port))

s.send(f"""<stream:stream
from='{jid}'
to='{dominio}'
version='1.0'
xml:lang='en'
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'>
""".encode('utf-8'))
print(s.recv(9999))

s.send(b"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
print(s.recv(9999))

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS)
ssl_context.verify_mode = ssl.CERT_REQUIRED
ssl_context.check_hostname = True
ssl_context.load_default_certs()
s = ssl_context.wrap_socket(s, server_hostname=dominio)

print('\nPasamos a TLS\n')

s.send(f"""<stream:stream
from='{jid}'
to='{dominio}'
version='1.0'
xml:lang='en'
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'>
""".encode('utf-8'))
print(s.recv(9999))

s.send(b"""
<auth mechanism='PLAIN'
xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</auth>
""" % base64.b64encode(f'{jid}\0{usuario}\0{clave}'.encode('utf-8')))
print(s.recv(9999))

s.send(f"""<stream:stream
from='{jid}'
to='{dominio}'
version='1.0'
xml:lang='en'
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'>
""".encode('utf-8'))
print(s.recv(9999))

s.send(b"""
<iq id='qwdc' type='set'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<resource>DUMMY</resource>
</bind>
</iq>
""")
print(s.recv(9999))

print("\nPreguntamos por qué extensiones soporta el servidor\n")

s.send(f"""
<iq from='{jid}' to='{dominio}' type='get' id='abcd'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>
""".encode('utf-8'))
print(s.recv(9999))

if jids:
    print('\nPedimos bloqueos:')

    # https://xmpp.org/extensions/xep-0191.html
    stanza = f"""
<iq from='{jid}/DUMMY' type='set' id='block1'>
<block xmlns='urn:xmpp:blocking'>
"""
    for jid in jids:
        print(f'Pedimos el bloqueo de "{jid}"')
        stanza += f"<item jid ='{jid}'/>\n"
    stanza +="""
</block>
</iq>
"""
    print("\nBloqueando...\n")
    s.send(stanza.encode('utf-8'))
    print(s.recv(9999))

print("\nLista de JID bloqueados:")
s.send(b"""
<iq type='get' id='blocklist1'>
<blocklist xmlns='urn:xmpp:blocking'/>
</iq>
""")

xml = xml.dom.minidom.parseString(s.recv(9999)).toprettyxml()
# Nos saltamos el '<?xml version="1.0" ?>' inicial.
print(xml[xml.find('\n') + 1:])

El código es básicamente el mismo que describo en Código para limpiar peticiones de suscripción de presencia espurias en el servidor XMPP Isode. Como explico en el artículo anterior, el código no es bueno ni bonito, pero funciona y quería invertir el mínimo tiempo posible.

Comentaré solo los cambios:

En la línea 20 leemos la lista de direcciones XMPP desde la línea de comandos. Lo hago de la forma más trivial posible, ya lo sé, Ya he dicho que este código no es bonito ni limpio.

El resto del programa es idéntico al del artículo anterior. Los cambios interesantes empiezan en la línea 94. Construimos la stanza solicitando el bloqueo en las líneas 98-108 y mostramos en pantalla lo que vamos a hacer (línea 103). Mandamos el bloqueo en la línea 110 e imprimimos la respuesta del servidor XMPP en la línea 111.

En las líneas 114-118 pedimos el listado de bloqueos actuales al servidor XMPP. Uso pretty printing porque la lista puede ser larga y es confuso mostrar la respuesta en una sola línea. Para ello, lo primero es construir el documento XML (línea 120) y luego lo imprimimos bonito en la línea 122.

XEP-0191: Blocking Command permite bloquear direcciones XMPP aisladas, dominios completos, etc. Puedes ver los detalles en la sección 6 (JID matching) del estándar. Por supuesto, el mismo estándar explica cómo eliminar bloqueos, aunque mi código actual no implementa esta funcionalidad. Tal vez lo añada como mejora futura.

Un ejemplo de respuesta, que no me importa compartir porque esas direcciones XMPP son fuentes de spam, es:

<iq to="jcea@jabber.org/DUMMY" type="result" id="blocklist1">
    <blocklist xmlns="urn:xmpp:blocking">
        <item jid="zoe3zd@skryre.org"/>
        <item jid="pvpctutorials.de"/>
        <item jid="kehn@hot-chilli.eu"/>
        <item jid="crystaldbd@nerv.tech"/>
        <item jid="home.zom.im"/>
        <item jid="quaesera.org"/>
        <item jid="arcaneflame@hot-chilli.eu"/>
        <item jid="conjudar@hot-chilli.eu"/>
        <item jid="alex2796@chinwag.im"/>
        <item jid="kleincaz@jabber.thelinuxnetworkx.rocks"/>
        <item jid="enigmer@xmpp-hosting.de"/>
        <item jid="darkengine.biz"/>
    </blocklist>
</iq>