pythonauthenticationaiosmtpd

Python aiosmtpd getting authentication working


I am trying to get authentication working using aiosmtpd... I have read the documentation but still can't get it working.

I have followed the instructions on this post: python aiosmtpd server with basic authentication , but still it isn't work

I have the following as the aiosmtpd server:


import logging
from aiosmtpd.controller import Controller
from aiosmtpd.smtp import AuthResult, LoginPassword

auth_db = {
    b"user1": b"password1",
    b"user2": b"password2",
    b"user3": b"password3",
}


# Authenticator function
def authenticator_func(server, session, envelope, mechanism, auth_data):
    # For this simple example, we'll ignore other parameters
    assert isinstance(auth_data, LoginPassword)
    username = auth_data.login
    password = auth_data.password

    # I am returning always AuthResult(success=True) just for testing
    if auth_db.get(username) == password:
        return AuthResult(success=True)
    else:
        return AuthResult(success=True)
        # return AuthResult(success=False, handled=False)


class CustomSMTPHandler:
    async def handle_DATA(self, server, session, envelope):
        print('End of message')
        return '250 Message accepted for delivery'


logging.basicConfig(level=logging.DEBUG)
handler = CustomSMTPHandler()
controller = Controller(
    handler,
    hostname='192.168.1.1',
    port=8025,
    authenticator=authenticator_func,  # i.e., the name of your authenticator function
    auth_required=True,  # Depending on your needs
)

controller.start()
input("Servidor iniciado. Presione Return para salir.")
controller.stop()

And this is the client:

import time

import yaml
import smtplib
import os
from smtplib import SMTPException
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText


# Variables ambiente
SMTP_SERVER = "192.168.1.1"
SMTP_PORT = 8025


def send_mail(mail_from, mail_to, mail_subject, mail_message):
    try:
        # Crea conexion a servidor
        smtp_server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
        smtp_server.set_debuglevel(True)
        # Creacion de mensage
        msg = MIMEMultipart()
        msg['From'] = mail_from
        msg['To'] = mail_to
        msg['Subject'] = mail_subject
        msg.attach(MIMEText(mail_message, 'html'))
        smtp_server.login('user1', 'password1')
        # Envio de correo
        smtp_server.send_message(msg)
        # Cierra conexion
        smtp_server.quit()
        # print("Email sent successfully!")
    except SMTPException as smtp_err:
        return {"Error": f"Error SMTP: {repr(smtp_err)}"}
        # print("SMTP Exception...", smtp_err)
    except Exception as gen_err:
        return {"Error": f"Error general: {repr(gen_err)}"}
        # print("Something went wrong….", gen_err)

    return {"Exito": f"Correo enviado a servidor SMTP: {SMTP_SERVER}"}


def main():
    st = time.time()
    for _ in range(1):
        resp = send_mail("test@sss.com",
                         "ch@test.com",
                         "Subject del correo",
                         "<b>Hola</b>")
        print(resp)
    et = time.time()
    elapsed_time = et - st
    print('Execution time:', elapsed_time, 'seconds')


if __name__ == "__main__":
    main()

I am getting the following as the server output:

INFO:mail.log:Available AUTH mechanisms: LOGIN(builtin) PLAIN(builtin)
INFO:mail.log:Peer: ('192.168.1.X', 54186)
INFO:mail.log:('192.168.1.X', 54186) handling connection
DEBUG:mail.log:('192.168.1.X', 54186) << b'220 PC-Name Python SMTP 1.4.5'
DEBUG:mail.log:_handle_client readline: b'EHLO [192.168.1.X]\r\n'
INFO:mail.log:('192.168.1.X', 54186) >> b'EHLO [192.168.1.X]'
DEBUG:mail.log:('192.168.1.X', 54186) << b'250-PC-Name'
DEBUG:mail.log:('192.168.1.X', 54186) << b'250-SIZE 33554432'
DEBUG:mail.log:('192.168.1.X', 54186) << b'250-8BITMIME'
DEBUG:mail.log:('192.168.1.X', 54186) << b'250-SMTPUTF8'
DEBUG:mail.log:('192.168.1.X', 54186) << b'250 HELP'
DEBUG:mail.log:_handle_client readline: b'MAIL FROM:<test@sss.com> BODY=8BITMIME SIZE=890\r\n'
INFO:mail.log:('192.168.1.X', 54186) >> b'MAIL FROM:<ch@test.com> BODY=8BITMIME SIZE=890'
INFO:mail.log:MAIL: Authentication required
DEBUG:mail.log:('192.168.1.X', 54186) << b'530 5.7.0 Authentication required'
INFO:mail.log:('192.168.1.X', 54186) EOF received
INFO:mail.log:('192.168.1.X', 54186) Connection lost during _handle_client()
INFO:mail.log:('192.168.1.X', 54186) connection lost

I am also not able to send emails using thunderbird.

Any ideas?

Posting client log after adding the line:

smtp_server.set_debuglevel(True)

to the send_mail function.

Log:

send: 'ehlo [192.168.1.X]\r\n'
reply: b'250-PC-Name-7010\r\n'
reply: b'250-SIZE 33554432\r\n'
reply: b'250-8BITMIME\r\n'
reply: b'250-SMTPUTF8\r\n'
reply: b'250 HELP\r\n'
reply: retcode (250); Msg: b'PC-Name\nSIZE 33554432\n8BITMIME\nSMTPUTF8\nHELP'
{'Error': "Error SMTP: SMTPNotSupportedError('SMTP AUTH extension not supported by server.')"}

Solution

  • Well, I've got it working... I changed code on both server (following advice from @tripleee about Authenticator instance) to handle authentication and on the client to send the "LOGIN" and/or "PLAIN" authentication method to the server (those are the methods aiosmtpd can handle by default).

    Here is the server code:

    import logging
    import aiosmtpd.controller
    from aiosmtpd.smtp import Envelope, AuthResult, LoginPassword
    from email import message_from_bytes
    from email.message import Message
    from email.policy import default
    
    
    class Authenticator:  
        def __call__(self, server, session, envelope, mechanism, auth_data):
            test_passwd = "1234"
            username = auth_data.login
            password = auth_data.password
            print(f'Username: {username}')
            print(f'Password: {password}')
            print(f'Password_test: {testpasswd}')
            
            if password == test_passwd:
                resp = AuthResult(success=True)
            else:
                resp = AuthResult(success=False, handled=False)
    
            return resp
    
    
    class CustomSMTPHandler:
        async def handle_DATA(self, server, session, envelope):
            """ Used to handle other stuff"""
            print('End of message')
            return '250 Message accepted for delivery'
    
    
    def main():
        handler = CustomSMTPHandler()
        auth = Authenticator()
        server = aiosmtpd.controller.Controller(handler,
                                                hostname='192.168.1.1',
                                                port=8025,
                                                authenticator=auth,
                                                auth_required=True,
                                                auth_require_tls=False
                                                )
        server.start()
        input("Servidor iniciado. Presione Return para salir.")
        server.stop()
    
    
    if __name__ == '__main__':
        logging.basicConfig(level=logging.DEBUG)
        main()
    

    Note: By defaul "auth_require_tls" is set to True for security reasons (as it should be!)... I changed parameter for testing.

    Here it is the client code:

    import time
    import smtplib
    import os
    from smtplib import SMTPException
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    
    
    SMTP_SERVER = "192.168.1.1"
    SMTP_PORT = 8025
    
    
    def send_mail(mail_from, mail_to, mail_subject, mail_message):
        try:
            smtp_server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
            smtp_server.set_debuglevel(True)
            msg = MIMEMultipart()
            msg['From'] = mail_from
            msg['To'] = mail_to
            msg['Subject'] = mail_subject
            msg.attach(MIMEText(mail_message, 'html'))
            smtp_server.esmtp_features['auth'] = 'PLAIN'
            smtp_server.login('user1', '1234')
            smtp_server.send_message(msg)
            smtp_server.quit()
        except SMTPException as smtp_err:
            return {"Error": f"Error SMTP: {repr(smtp_err)}"}
        except Exception as gen_err:
            return {"Error": f"Error general: {repr(gen_err)}"}
    
        return {"Exito": f"Correo enviado a servidor SMTP: {SMTP_SERVER}"}
    
    
    def main():
        st = time.time()
        for _ in range(1):
            resp = send_mail("test@sss.com",
                             "ch@test.com",
                             "Subject del correo",
                             "<b>Hola</b>")
            print(resp)
        et = time.time()
        elapsed_time = et - st
        print('Execution time:', elapsed_time, 'seconds')
    
    
    if __name__ == "__main__":
        main()
    

    The change on the client is the "smtp_server.esmtp_features['auth'] = 'PLAIN'" line who tells the authentication is going to be "PLAIN"... You can specify many types like for example: "smtp_server.esmtp_features['auth'] = 'LOGIN PLAIN'".