Chucha Cuta
Chucha Cuta

Reputation: 13

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("[email protected]",
                         "[email protected]",
                         "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:<[email protected]> BODY=8BITMIME SIZE=890\r\n'
INFO:mail.log:('192.168.1.X', 54186) >> b'MAIL FROM:<[email protected]> 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.')"}

Upvotes: 0

Views: 399

Answers (2)

Chucha Cuta
Chucha Cuta

Reputation: 13

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("[email protected]",
                         "[email protected]",
                         "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'".

Upvotes: 1

tripleee
tripleee

Reputation: 189628

The debug error log indirectly tells you what's wrong: The list of supported ESMTP extensions advertised by the server in its EHLO response does not mention AUTH, so the client code (correctly) refuses to try to use this optional protocol extension, and therefore your login call fails.

Your code doesn't show some parts, so this is hard to detect from looking at the code alone; thanks for providing the debug log, and I hope you'll turn to it more in the future to figure out what's wrong when something isn't working.

In some more detail, the base SMTP protocol only supports the basic transport protocol, with no provisions for e.g. 8-bit DATA. The ESMTP specification extended the protocol so that if the client greets the server with EHLO instead of HELO, this enables a mechanism for the server to offer additional facilities to the client (IIRC the SIZE extension is mandatory, maybe 8BITMIME too? But other than that, what exactly to offer is up to the server's capabilities and configuration).

The de facto standard these days is to offer the AUTH extension (with a potentially growing palette of implementation options, starting from very basic cleartext to various challenge/response etc authentication mechanisms) and STARTTLS to pivot from an unencrypted connection to an encrypted one (ideally before transmitting any credentials!)

I'm not familiar with aiosmtpd, but the documentation seems to be implying that the authenticator keyword argument needs to be an Authenticator instance.

Upvotes: 0

Related Questions