bluetiger9 / SmtpClient-for-Qt

An SMTP Client writen in C++ for Qt. Allows applications to send emails (MIME with text, html, attachments, inline files, etc.) via SMTP. Supports SSL and SMTP authentication.
https://github.com/bluetiger9/SmtpClient-for-Qt/wiki
GNU Lesser General Public License v2.1
443 stars 228 forks source link

Crash with free invalid pointer in MimeMultiPart class #158

Closed ocgltd closed 1 day ago

ocgltd commented 1 month ago

See fourth post for cause of error and potential solution

I am building an SMTP client as per the wiki example, using Qt 6.5.0. I am trying to connect to localhost:25, but I do not have any mail server (nothing listening on that port). The smtp client crashes during the connectToHost method, and stderr/stdout shows:

[SmtpClient] State: ConnectingState
[Socket] State: QAbstractSocket::HostLookupState
[Socket] State: QAbstractSocket::ConnectingState
[Socket] State: QAbstractSocket::UnconnectedState
[SmtpClient] State: UnconnectedState
[Socket] ERROR: QAbstractSocket::ConnectionRefusedError
free(): invalid pointer
[SmtpClient] State: ConnectingState
[Socket] State: QAbstractSocket::HostLookupState
[Socket] State: QAbstractSocket::ConnectingState
[Socket] State: QAbstractSocket::UnconnectedState
[SmtpClient] State: UnconnectedState
[Socket] ERROR: QAbstractSocket::ConnectionRefusedError
free(): invalid pointer

I am sending an HTML body message, but this occurs with just text body as well.

mozhongzhou commented 1 month ago

自动回复:已收到您的邮件

ocgltd commented 1 month ago

I tried again by connecting to a working mta, and now the class progresses further but again crashes (with SIGABRT), on the waitForMailSent method.

[SmtpClient] State: ConnectingState
[Socket] State: QAbstractSocket::HostLookupState
[Socket] State: QAbstractSocket::ConnectingState
[Socket] State: QAbstractSocket::ConnectedState
[SmtpClient] State: ConnectedState
[Socket] IN:  "220 exchange.obfuscated.com Microsoft ESMTP MAIL Service ready at Tue, 11 Jun 2024 10:27:35 -0400\r\n"
[SmtpClient] State: _EHLO_State
[Socket] OUT: "EHLO localhost"
[Socket] IN:  "250-exchange.obfuscated.com Hello [172.31.254.144]\r\n"
[Socket] IN:  "250-SIZE 37748736\r\n"
[Socket] IN:  "250-PIPELINING\r\n"
[Socket] IN:  "250-DSN\r\n"
[Socket] IN:  "250-ENHANCEDSTATUSCODES\r\n"
[Socket] IN:  "250-STARTTLS\r\n"
[Socket] IN:  "250-X-ANONYMOUSTLS\r\n"
[Socket] IN:  "250-AUTH NTLM\r\n"
[Socket] IN:  "250-X-EXPS GSSAPI NTLM\r\n"
[Socket] IN:  "250-8BITMIME\r\n"
[Socket] IN:  "250-BINARYMIME\r\n"
[Socket] IN:  "250-CHUNKING\r\n"
[Socket] IN:  "250-SMTPUTF8\r\n"
[Socket] IN:  "250 XRDST\r\n"
[SmtpClient] State: _READY_Connected
[SmtpClient] State: ReadyState
[SmtpClient] State: MailSendingState
[SmtpClient] State: _MAIL_0_FROM
[Socket] OUT: "MAIL FROM:<sample@pbx1.mydomain.com>"
[SmtpClient] State: ConnectingState
[Socket] State: QAbstractSocket::HostLookupState
[Socket] State: QAbstractSocket::ConnectingState
[Socket] IN:  "250 2.1.0 Sender OK\r\n"
[SmtpClient] State: _MAIL_1_RCPT_INIT
[SmtpClient] State: _MAIL_2_RCPT
[Socket] OUT: "RCPT TO:<support@mydomain.com>"
[Socket] State: QAbstractSocket::ConnectedState
[SmtpClient] State: ConnectedState
[Socket] IN:  "550 5.7.54 SMTP; Unable to relay recipient in non-accepted domain\r\n"
free(): invalid pointer
[Socket] IN:  "220 exchange.obfuscated.com Microsoft ESMTP MAIL Service ready at Tue, 11 Jun 2024 10:27:35 -0400\r\n"
[SmtpClient] State: _EHLO_State
[Socket] OUT: "EHLO localhost"
[Socket] IN:  "250-exchange.obfuscated.com Hello [172.31.254.144]\r\n"
[Socket] IN:  "250-SIZE 37748736\r\n"
[Socket] IN:  "250-PIPELINING\r\n"
[Socket] IN:  "250-DSN\r\n"
[Socket] IN:  "250-ENHANCEDSTATUSCODES\r\n"
[Socket] IN:  "250-STARTTLS\r\n"
[Socket] IN:  "250-X-ANONYMOUSTLS\r\n"
[Socket] IN:  "250-AUTH NTLM\r\n"
[Socket] IN:  "250-X-EXPS GSSAPI NTLM\r\n"
[Socket] IN:  "250-8BITMIME\r\n"
[Socket] IN:  "250-BINARYMIME\r\n"
[Socket] IN:  "250-CHUNKING\r\n"
[Socket] IN:  "250-SMTPUTF8\r\n"
[Socket] IN:  "250 XRDST\r\n"
[SmtpClient] State: _READY_Connected
[SmtpClient] State: ReadyState
[SmtpClient] State: MailSendingState
[SmtpClient] State: _MAIL_0_FROM
[Socket] OUT: "MAIL FROM:<example@pbx1.mydomain.com>"
[Socket] IN:  "250 2.1.0 Sender OK\r\n"
[SmtpClient] State: _MAIL_1_RCPT_INIT
[SmtpClient] State: _MAIL_2_RCPT
[Socket] OUT: "RCPT TO:<support@mydomain.com>"

And here's the call stack upon crash:

1   __pthread_kill_implementation              pthread_kill.c             44   0x7ffff6ea154c 
2   __pthread_kill_internal                    pthread_kill.c             78   0x7ffff6ea15b3 
3   __GI_raise                                 raise.c                    26   0x7ffff6e54d46 
4   __GI_abort                                 abort.c                    79   0x7ffff6e287f3 
5   __libc_message                             libc_fatal.c               150  0x7ffff6e29130 
6   malloc_printerr                            malloc.c                   5515 0x7ffff6eab617 
7   _int_free                                  malloc.c                   4306 0x7ffff6eacecc 
8   __GI___libc_free                           malloc.c                   3258 0x7ffff6eaf955 
9   MimePart::~MimePart                        mimepart.cpp               39   0x44e4f9       
10  MimeMultiPart::~MimeMultiPart              mimemultipart.cpp          48   0x44f2d3       
11  MimeMultiPart::~MimeMultiPart              mimemultipart.cpp          50   0x44f344       
12  MimeMessage::~MimeMessage                  mimemessage.cpp            41   0x451606       
13  Alert::send_email                          alert.cpp                  118  0x44708f       
14  Alert::send_alert_generic                  alert.cpp                  324  0x448477       

The error occurs in the delete line of the method below; I'm guessing a pointer problem?

MimeMultiPart::~MimeMultiPart() {
    foreach (MimePart *part, parts) {
        delete part;
    }
}
ocgltd commented 1 month ago

Just in case I'm calling something wrong, I'll show my code...

    MimeMessage message;
    EmailAddress sender(programSettings->smtp_senderemail(),programSettings->smtp_sendername());
    EmailAddress recipient( programSettings->alerts_recipientemail(),programSettings->alerts_recipientname());
    message.setSender(sender);
    message.addRecipient(recipient);
    message.setSubject(email_subject);

    MimeHtml html;
    MimeText text;

    // Create a MimeText object.
    if (email_usehtml) {
        html.setHtml(email_body);
        message.addPart(&html);
    } else {
        text.setText(email_body);
        message.addPart(&text);
    }

    SmtpClient smtp(programSettings->smtp_hostname(), programSettings->smtp_port(), programSettings>smtp_encryption());

    smtp.connectToHost();
    if (!smtp.waitForReadyConnected()) {
        return false;
    }

    if (programSettings->smtp_useauthentication()) {
        smtp.login(programSettings->smtp_username(),programSettings->smtp_password(),SmtpClient::AuthLogin);
        if (!smtp.waitForAuthenticated()) {
            return false;
        }
    }

    smtp.sendMail(message);
    if (!smtp.waitForMailSent()) {
        return false;
    }

    smtp.quit();
    return true;

and I should note that even if a message is successfully sent, the smtpclient crashes! Something in the MimeMultiPart seems to lose track of "parts" (MimePart classes).

ocgltd commented 1 month ago

Looking at the examples and source, the message.addpart(&mimetextpart) adds a part to the message. But internally it adds a pointer to the original object (which was created on the stack by the caller). But, the mimemultipart destructor tries to delete these MimeText variables. Why is it trying to delete an object from the stack created by the caller?

I tried modifying my code to call smtpclient with body parts created on the heap instead of stack (and not deleting them myself):

   // Fill MimeMessage body
    MimeHtml* htmlPtr = new MimeHtml;
    MimeText* textPtr = new MimeText;
    if (email_usehtml) {
        htmlPtr->setHtml(email_body);
        message.addPart(htmlPtr);
    } else {
        textPtr->setText(email_body);
        message.addPart(textPtr);
    }
    // Do not delete MimeXXXX since smtp lib will delete these

... and now the code runs without crash. (But, valgrind reports the 2 MimeXXXX objects cause lost memory blocks). Does the smtpclient code need to be changed to make a copy of the mimepart sent to message.addPart ? Am I calling something wrong? Something isn't right here....

bluetiger9 commented 1 day ago

This should be fixed now. I changed the .addPart() method to take ownership of the given part only if requested with the takeOwnership argument.