Aller au contenu

AUTH LOGIN manual en Python con smtplib: caracteres especiales y error 535

Rogelio Guerra Riverón
Auteur
Rogelio Guerra Riverón
Construction de ma propre infrastructure web depuis zéro. Je documente chaque étape : serveurs, réseaux, conteneurs et tout ce qui se présente.

Le problème
#

Il y a quelques jours, j’ai tenté d’automatiser l’envoi de courriels depuis mon serveur domestique. Rien de compliqué : un script Python avec smtplib pour envoyer des notifications. Le problème est arrivé quand j’ai configuré un mot de passe avec des caractères spéciaux : MiPasw0rd$Ñ.

import smtplib

server = smtplib.SMTP('mail.example.com', 587)
server.starttls()
server.login('usuario@example.com', 'MiPasw0rd$Ñ')  # Error 535

Résultat : SMTPAuthenticationError: (535, b'5.7.8 Authentication credentials invalid')

L’étrange, c’est que le mot de passe était correct. J’y accédais manuellement sans problème. L’erreur 535 suggérait des échecs d’authentification, mais le vrai problème était dans l’encodage.

Pourquoi ça échoue
#

C’est la faute de la gestion de l’encodage dans smtplib. La méthode login() utilise par défaut UTF-8, mais applique ensuite des transformations qui ne respectent pas toujours correctement les caractères spéciaux, particulièrement quand le serveur ou la libraire ont des configurations héritées.

Dans mon cas, le serveur SMTP s’attendait à un encodage UTF-8 cohérent. Quand smtplib traitait le mot de passe avec $ et Ñ, quelque chose se corrompait en chemin.

La solution : AUTH LOGIN manuel
#

Le protocole AUTH LOGIN est simple : on encode l’utilisateur et le mot de passe en base64 séparément et on les envoie par étapes manuelles. Cela te donne un contrôle total sur l’encodage.

import smtplib
import base64

def auth_login_manual(server, usuario, contraseña):
    """Implementa AUTH LOGIN manualmente con UTF-8"""
    
    # Respuesta inicial del servidor
    code, response = server.docmd("AUTH LOGIN")
    
    # Codificar usuario en base64 UTF-8
    usuario_b64 = base64.b64encode(usuario.encode('utf-8')).decode('ascii')
    code, response = server.docmd(usuario_b64)
    
    # Codificar contraseña en base64 UTF-8
    contraseña_b64 = base64.b64encode(contraseña.encode('utf-8')).decode('ascii')
    code, response = server.docmd(contraseña_b64)
    
    # Verificar éxito (código 235 es autenticación exitosa)
    if code != 235:
        raise Exception(f"Autenticación fallida: {code} {response}")
    
    return True

# Uso
server = smtplib.SMTP('mail.example.com', 587)
server.starttls()
auth_login_manual(server, 'usuario@example.com', 'MiPasw0rd$Ñ')
print("Autenticado exitosamente")

Détail du protocole
#

  1. AUTH LOGIN : Le client demande d’utiliser le mécanisme AUTH LOGIN
  2. Utilisateur en base64 : Le serveur répond avec 334, attend l’utilisateur encodé
  3. Mot de passe en base64 : Le serveur répond avec 334, attend le mot de passe encodé
  4. Réponse 235 : Indique une authentification réussie

La méthode docmd() envoie des commandes SMTP brutes et retourne le code de réponse et le message.

Test sur mon serveur
#

J’ai implémenté cela dans mon infrastructure domestique avec Postfix. La différence est notable :

# Antes: falla con caracteres especiales
server.login('admin@local.home', 'P@ssw0rdÑ')  # Error 535

# Después: funciona perfectamente
auth_login_manual(server, 'admin@local.home', 'P@ssw0rdÑ')  # OK

Considérations de sécurité
#

  • Base64 n’est pas du chiffrement : utilise toujours STARTTLS ou une connexion directe au port 465 avec SSL
  • L’encodage UTF-8 est sûr pour les caractères spéciaux
  • Cette méthode est compatible avec n’importe quel serveur SMTP qui supporte AUTH LOGIN

Script complet
#

import smtplib
import base64

class SMTPConnection:
    def __init__(self, host, port=587):
        self.server = smtplib.SMTP(host, port)
        self.server.starttls()
    
    def login(self, usuario, contraseña):
        self.server.docmd("AUTH LOGIN")
        self.server.docmd(base64.b64encode(usuario.encode('utf-8')).decode('ascii'))
        code, resp = self.server.docmd(base64.b64encode(contraseña.encode('utf-8')).decode('ascii'))
        if code != 235:
            raise Exception(f"Auth failed: {code}")
    
    def send(self, de, para, asunto, cuerpo):
        msg = f"From: {de}\nTo: {para}\nSubject: {asunto}\n\n{cuerpo}"
        self.server.sendmail(de, para, msg)
    
    def close(self):
        self.server.quit()

# Uso
smtp = SMTPConnection('mail.example.com')
smtp.login('usuario@example.com', 'MiPasw0rd$Ñ')
smtp.send('usuario@example.com', 'destino@example.com', 'Test', 'Funciona!')
smtp.close()

Conclusion
#

Si ton serveur SMTP rejette les mots de passe avec des caractères spéciaux, ce n’est pas un mystère. C’est un problème d’encodage. Implémenter AUTH LOGIN manuellement te donne un contrôle total et fonctionne avec n’importe quel mot de passe.

Je l’ai appliqué en production sur mon serveur domestique et je n’ai eu aucun problème depuis.


Équipement recommandé
#

Liens d’affiliation. Aucun coût supplémentaire pour toi.