I am trying to create a si开发者_JS百科mple send mail function for a custom app created with the Qt framework in C++. I am very close just at the point where SMTP is trying to authenticate and can't for the life of me to get this to work. Here is what I am working with
do {
responseLine = socket->readLine();
response += responseLine;
}
while ( socket->canReadLine() && responseLine[3] != ' ' );
responseLine.truncate( 3 );
if ( state == Init && responseLine[0] == '2' ) {
// banner was okay, let's go on
*t << "HELO there\r\n";
t->flush();
state = Auth;
} else if (state == Auth && responseLine[0] == '2') {
*t << "STARTTLS\r\n";
*t << "AUTH PLAIN\r\n";
t->flush();
state = User;
} else if (state == User && responseLine[0] == '2') {
*t << "username\r\n";
t->flush();
state = Pass;
} else if (state == Pass && responseLine[0] == '2') {
*t << "pass\r\n";
t->flush();
state = Mail;
} else if ( state == Mail && responseLine[0] == '2' ) {
// HELO response was okay (well, it has to be)
*t << "MAIL FROM: " << from << "\r\n";
t->flush();
state = Rcpt;
} else if ( state == Rcpt && responseLine[0] == '2' ) {
*t << "RCPT TO: " << rcpt << "\r\n"; //r
t->flush();
state = Data;
} else if ( state == Data && responseLine[0] == '2' ) {
*t << "DATA\r\n";
t->flush();
state = Body;
} else if ( state == Body && responseLine[0] == '3' ) {
*t << message << "\r\n.\r\n";
t->flush();
state = Quit;
} else if ( state == Quit && responseLine[0] == '2' ) {
*t << "QUIT\r\n";
t->flush();
// here, we just close.
state = Close;
emit status( tr( "Message sent" ) );
} else if ( state == Close ) {
deleteLater();
return;
} else {
// something broke.
You're using a QTextStream? That seems wrong. QTextStream is for writing encoded text and will mess with both encoding and line endings and that's not what you want when implementing a protocol. Better use QByteArray (instead of QString) and QDataStream instead of QTextStream, or for better error handling, write directly to the socket.
Is it an encryption problem? I can see you're using STARTTLS, but I cannot see how you handle the actual en-/decryption of the subsequent lines...
According to RFC 2595, encryption must start immediately after the OK response to the STARTTLS command. Your code suggests that you do not even wait for that OK, instead you send AUTH PLAIN immediately, resulting in plain text continuation from your client while the server expects encryption.
Edit: If you don't need encryption, just leave out STARTTLS and stick to the (E)SMTP standard as defined in the RFCs ... For ESMTP Auth you may need to use EHLO instead of HELO, else the server may not understand AUTH.
You'll find all SMTP-related RFCs on the corresponding Wikipedia page. Most notably, you'll need RFC5321 (most recent summary of SMTP) and RFC4954 (most recent description of ESMTP AUTH). But ... it may be more complicated than you think. There may be some SMTP servers that do not accept AUTH PLAIN when the connection is not encrypted. You'd need to evaluate the server's response to the EHLO command (see the example section in RFC4954).
I think it would be easier for you to just use a library. I do not know one for C++, you need to find it by yourself.
Checking responseline[0] isn't the best thing...when you request 'AUTH PLAIN' or 'AUTH LOGIN', the server will respond with a username request (followed by a password request, after you write the username) of the form '334 VXNlcm5hbWU6'
(password request is the same form) ...so when you're checking to see if responseLine[0] == '2'
you're saying there is an error, when in reality there is no error. So I would change the code below to check for more than just the first character of the response line.
else if (state == User && responseLine[0] == '2') {
*t << "username\r\n";
t->flush();
state = Pass;
} else if (state == Pass && responseLine[0] == '2') {
*t << "pass\r\n";
t->flush();
state = Mail;
you could do something like else if (state == Pass && responseLine.contains("334")
UPDATE
'334 VXNlcm5hbWU6'
is only when you use AUTH LOGIN, if you use AUTH PLAIN you will simply get a 334
from the server.
Also, you may need to encode you username/pass to base64
Try simple C++ ssl smtp email client works perfect https://github.com/breakermind/SslSMTPClient You can send email without smtp server from your application (get mx hosts from dns and send to server):
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
// openssl
#include <openssl/ssl.h>
#include <openssl/err.h>
// apt-get install libssl-dev openssl
// g++ -o start main.cpp sslsmtpex.cpp sslsmtpex.h -lssl -lresolv -lcrypto -sd=c++11 -std=c++14
// Include client
#include "sslsmtpex.h"
using namespace std;
// main - create SSL context and connect
int main(int count, char *strings[])
{
cout << "C++ ssl smtp send email with STARTTLS\r\n";
// Add attachments to message if you want
vector<string> files;
// files.push_back("file9.jpg");
// files.push_back("filek.pdf");
// Initialize
sslsmtpEx sm;
sm.sslsmtpExSet("localhost", 25);
// EHLO hostname
sm.heloHostname("qflash.pl");
// Display logs
// sm.showLogs();
// get MX records from dns for recipient
vector<string> mx = sm.getMX("nanomoow@gmail.com",0,0);
// Send email to each mx host from recipient domain DNS ( You need send only to one server !!! )
for (int i = 0; i < mx.size(); i++){
// Set hostname from mx dns
sm.sslsmtpExSet(mx.at(i), 25);
cout << "Mx host: " << mx.at(i) << endl;
// send email
int ok = sm.Send("email@qflash.pl", "nanomoow@gmail.com", "email@qflash.pl", "Smtp client test", "<h1>Smtp test</h1>", "<h1>Smtp test</h1>", files);
cout << "Email has been sent : " << ok << endl;
if(ok){
// if email has been sent, end loop with next mxhost
break;
}
}
sleep(10);
return 0;
}
精彩评论