2013-06-24 13:32:40 +00:00
|
|
|
//
|
|
|
|
// VMime library (http://www.vmime.org)
|
2018-09-05 21:54:48 +00:00
|
|
|
// Copyright (C) 2002 Vincent Richard <vincent@vmime.org>
|
2013-06-24 13:32:40 +00:00
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 3 of
|
|
|
|
// the License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License along
|
|
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
//
|
|
|
|
// Linking this library statically or dynamically with other modules is making
|
|
|
|
// a combined work based on this library. Thus, the terms and conditions of
|
|
|
|
// the GNU General Public License cover the whole combination.
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
/** Accepts connection and fails on greeting.
|
|
|
|
*/
|
2018-09-05 21:54:48 +00:00
|
|
|
class greetingErrorSMTPTestSocket : public lineBasedTestSocket {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
public:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void onConnected() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("421 test.vmime.org Service not available, closing transmission channel\r\n");
|
|
|
|
disconnect();
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void processCommand() {
|
|
|
|
|
|
|
|
if (!haveMoreLines()) {
|
2013-06-24 13:32:40 +00:00
|
|
|
return;
|
2018-09-05 21:54:48 +00:00
|
|
|
}
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
getNextLine();
|
|
|
|
|
|
|
|
localSend("502 Command not implemented\r\n");
|
|
|
|
processCommand();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** SMTP test server 1.
|
|
|
|
*
|
|
|
|
* Test send().
|
|
|
|
* Ensure MAIL and RCPT commands are sent correctly.
|
|
|
|
*/
|
2018-09-05 21:54:48 +00:00
|
|
|
class MAILandRCPTSMTPTestSocket : public lineBasedTestSocket {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
public:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
MAILandRCPTSMTPTestSocket() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
m_recipients.insert("recipient1@test.vmime.org");
|
|
|
|
m_recipients.insert("recipient2@test.vmime.org");
|
|
|
|
m_recipients.insert("recipient3@test.vmime.org");
|
|
|
|
|
|
|
|
m_state = STATE_NOT_CONNECTED;
|
|
|
|
m_ehloSent = m_heloSent = m_mailSent = m_rcptSent = m_dataSent = m_quitSent = false;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
~MAILandRCPTSMTPTestSocket() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("Client must send the DATA command", m_dataSent);
|
|
|
|
VASSERT("Client must send the QUIT command", m_quitSent);
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void onConnected() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("220 test.vmime.org Service ready\r\n");
|
|
|
|
processCommand();
|
|
|
|
|
|
|
|
m_state = STATE_COMMAND;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void processCommand() {
|
|
|
|
|
|
|
|
if (!haveMoreLines()) {
|
2013-06-24 13:32:40 +00:00
|
|
|
return;
|
2018-09-05 21:54:48 +00:00
|
|
|
}
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
vmime::string line = getNextLine();
|
|
|
|
std::istringstream iss(line);
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
switch (m_state) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
case STATE_NOT_CONNECTED:
|
|
|
|
|
|
|
|
localSend("451 Requested action aborted: invalid state\r\n");
|
|
|
|
break;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
case STATE_COMMAND: {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
std::string cmd;
|
|
|
|
iss >> cmd;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (cmd.empty()) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("500 Syntax error, command unrecognized\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "EHLO") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("502 Command not implemented\r\n");
|
|
|
|
|
|
|
|
m_ehloSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "HELO") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("Client must send the EHLO command before HELO", m_ehloSent);
|
|
|
|
|
|
|
|
localSend("250 OK\r\n");
|
|
|
|
|
|
|
|
m_heloSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "MAIL") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("Client must send the HELO command", m_heloSent);
|
|
|
|
VASSERT("The MAIL command must be sent only one time", !m_mailSent);
|
|
|
|
|
|
|
|
VASSERT_EQ("MAIL", std::string("MAIL FROM:<expeditor@test.vmime.org>"), line);
|
|
|
|
|
|
|
|
localSend("250 OK\r\n");
|
|
|
|
|
|
|
|
m_mailSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "RCPT") {
|
|
|
|
|
2013-12-10 07:52:51 +00:00
|
|
|
const vmime::size_t lt = line.find('<');
|
|
|
|
const vmime::size_t gt = line.find('>');
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
VASSERT("RCPT <", lt != vmime::string::npos);
|
|
|
|
VASSERT("RCPT >", gt != vmime::string::npos);
|
|
|
|
VASSERT("RCPT ><", gt >= lt);
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
const vmime::string recip =
|
|
|
|
vmime::string(line.begin() + lt + 1, line.begin() + gt);
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
std::set <vmime::string>::iterator it =
|
|
|
|
m_recipients.find(recip);
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
VASSERT(
|
|
|
|
std::string("Recipient not found: '") + recip + "'",
|
|
|
|
it != m_recipients.end()
|
|
|
|
);
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
m_recipients.erase(it);
|
|
|
|
|
|
|
|
localSend("250 OK, recipient accepted\r\n");
|
|
|
|
|
|
|
|
m_rcptSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "DATA") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("Client must send the MAIL command", m_mailSent);
|
|
|
|
VASSERT("Client must send the RCPT command", m_rcptSent);
|
|
|
|
VASSERT("All recipients", m_recipients.empty());
|
|
|
|
|
|
|
|
localSend("354 Ready to accept data; end with <CRLF>.<CRLF>\r\n");
|
|
|
|
|
|
|
|
m_state = STATE_DATA;
|
|
|
|
m_msgData.clear();
|
|
|
|
|
|
|
|
m_dataSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "NOOP") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("250 Completed\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "QUIT") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
m_quitSent = true;
|
|
|
|
|
|
|
|
localSend("221 test.vmime.org Service closing transmission channel\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("502 Command not implemented\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2018-09-05 21:54:48 +00:00
|
|
|
case STATE_DATA: {
|
|
|
|
|
|
|
|
if (line == ".") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT_EQ("Data", "Message data\r\n", m_msgData);
|
|
|
|
|
|
|
|
localSend("250 Message accepted for delivery\r\n");
|
|
|
|
m_state = STATE_COMMAND;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
m_msgData += line + "\r\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
processCommand();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
enum State {
|
2013-06-24 13:32:40 +00:00
|
|
|
STATE_NOT_CONNECTED,
|
|
|
|
STATE_COMMAND,
|
|
|
|
STATE_DATA
|
|
|
|
};
|
|
|
|
|
|
|
|
int m_state;
|
|
|
|
|
|
|
|
std::set <vmime::string> m_recipients;
|
|
|
|
|
|
|
|
std::string m_msgData;
|
|
|
|
|
|
|
|
bool m_ehloSent, m_heloSent, m_mailSent, m_rcptSent,
|
|
|
|
m_dataSent, m_quitSent;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** SMTP test server 2.
|
|
|
|
*
|
|
|
|
* Test CHUNKING extension/BDAT command.
|
|
|
|
*/
|
2018-09-05 21:54:48 +00:00
|
|
|
class chunkingSMTPTestSocket : public testSocket {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
public:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
chunkingSMTPTestSocket() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
m_state = STATE_NOT_CONNECTED;
|
|
|
|
m_bdatChunkCount = 0;
|
|
|
|
m_ehloSent = m_mailSent = m_rcptSent = m_quitSent = false;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
~chunkingSMTPTestSocket() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT_EQ("BDAT chunk count", 3, m_bdatChunkCount);
|
|
|
|
VASSERT("Client must send the QUIT command", m_quitSent);
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void onConnected() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("220 test.vmime.org Service ready\r\n");
|
|
|
|
processCommand();
|
|
|
|
|
|
|
|
m_state = STATE_COMMAND;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void onDataReceived() {
|
|
|
|
|
|
|
|
if (m_state == STATE_DATA) {
|
|
|
|
|
|
|
|
if (m_bdatChunkReceived != m_bdatChunkSize) {
|
|
|
|
|
2013-12-10 07:52:51 +00:00
|
|
|
const size_t remaining = m_bdatChunkSize - m_bdatChunkReceived;
|
|
|
|
const size_t received = localReceiveRaw(NULL, remaining);
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
m_bdatChunkReceived += received;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (m_bdatChunkReceived == m_bdatChunkSize) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
m_state = STATE_COMMAND;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
processCommand();
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void processCommand() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
vmime::string line;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (!localReceiveLine(line)) {
|
2013-06-24 13:32:40 +00:00
|
|
|
return;
|
2018-09-05 21:54:48 +00:00
|
|
|
}
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
std::istringstream iss(line);
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
switch (m_state) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
case STATE_NOT_CONNECTED:
|
|
|
|
|
|
|
|
localSend("451 Requested action aborted: invalid state\r\n");
|
|
|
|
break;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
case STATE_COMMAND: {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
std::string cmd;
|
|
|
|
iss >> cmd;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (cmd == "EHLO") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("250-test.vmime.org says hello\r\n");
|
|
|
|
localSend("250 CHUNKING\r\n");
|
|
|
|
|
|
|
|
m_ehloSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "HELO") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("Client must not send the HELO command, as EHLO succeeded", false);
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "MAIL") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("The MAIL command must be sent only one time", !m_mailSent);
|
|
|
|
|
|
|
|
localSend("250 OK\r\n");
|
|
|
|
|
|
|
|
m_mailSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "RCPT") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("250 OK, recipient accepted\r\n");
|
|
|
|
|
|
|
|
m_rcptSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "DATA") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("BDAT must be used here!", false);
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "BDAT") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("Client must send the MAIL command", m_mailSent);
|
|
|
|
VASSERT("Client must send the RCPT command", m_rcptSent);
|
|
|
|
|
|
|
|
unsigned long chunkSize = 0;
|
|
|
|
iss >> chunkSize;
|
|
|
|
|
|
|
|
std::string last;
|
|
|
|
iss >> last;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (m_bdatChunkCount == 0) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT_EQ("BDAT chunk1 size", 262144, chunkSize);
|
|
|
|
VASSERT_EQ("BDAT chunk1 last", "", last);
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (m_bdatChunkCount == 1) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT_EQ("BDAT chunk2 size", 262144, chunkSize);
|
|
|
|
VASSERT_EQ("BDAT chunk2 last", "", last);
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (m_bdatChunkCount == 2) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT_EQ("BDAT chunk3 size", 4712, chunkSize);
|
|
|
|
VASSERT_EQ("BDAT chunk3 last", "LAST", last);
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("No more BDAT command should be issued!", false);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_bdatChunkSize = chunkSize;
|
|
|
|
m_bdatChunkReceived = 0;
|
|
|
|
m_bdatChunkCount++;
|
|
|
|
m_state = STATE_DATA;
|
|
|
|
|
|
|
|
localSend("250 chunk received\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "NOOP") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("250 Completed\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "QUIT") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("221 test.vmime.org Service closing transmission channel\r\n");
|
|
|
|
|
|
|
|
m_quitSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("502 Command not implemented\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
processCommand();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
enum State {
|
2013-06-24 13:32:40 +00:00
|
|
|
STATE_NOT_CONNECTED,
|
|
|
|
STATE_COMMAND,
|
|
|
|
STATE_DATA
|
|
|
|
};
|
|
|
|
|
|
|
|
int m_state;
|
|
|
|
int m_bdatChunkCount;
|
2016-03-13 19:15:22 +00:00
|
|
|
size_t m_bdatChunkSize, m_bdatChunkReceived;
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
bool m_ehloSent, m_mailSent, m_rcptSent, m_quitSent;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
class SMTPTestMessage : public vmime::message {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
public:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
vmime::size_t getChunkBufferSize() const {
|
|
|
|
|
2014-01-05 12:53:44 +00:00
|
|
|
static vmime::net::smtp::SMTPChunkingOutputStreamAdapter chunkStream(vmime::null, 0, NULL);
|
2013-06-24 13:32:40 +00:00
|
|
|
return chunkStream.getBlockSize();
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
const std::vector <vmime::string>& getChunks() const {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
static std::vector <vmime::string> chunks;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (chunks.size() == 0) {
|
2013-06-24 13:32:40 +00:00
|
|
|
chunks.push_back(vmime::string(1000, 'A'));
|
|
|
|
chunks.push_back(vmime::string(3000, 'B'));
|
|
|
|
chunks.push_back(vmime::string(500000, 'C'));
|
|
|
|
chunks.push_back(vmime::string(25000, 'D'));
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunks;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void generateImpl(
|
|
|
|
const vmime::generationContext& /* ctx */,
|
|
|
|
vmime::utility::outputStream& outputStream,
|
|
|
|
const size_t /* curLinePos */ = 0,
|
|
|
|
size_t* /* newLinePos */ = NULL
|
|
|
|
) const {
|
|
|
|
|
|
|
|
for (size_t i = 0, n = getChunks().size() ; i < n ; ++i) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
const vmime::string& chunk = getChunks()[i];
|
|
|
|
outputStream.write(chunk.data(), chunk.size());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** SMTP test server 3.
|
|
|
|
*
|
|
|
|
* Test SIZE extension.
|
|
|
|
*/
|
|
|
|
template <bool WITH_CHUNKING>
|
2018-09-05 21:54:48 +00:00
|
|
|
class bigMessageSMTPTestSocket : public testSocket {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
public:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
bigMessageSMTPTestSocket() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
m_state = STATE_NOT_CONNECTED;
|
|
|
|
m_ehloSent = m_mailSent = m_rcptSent = m_quitSent = false;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
~bigMessageSMTPTestSocket() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("Client must send the QUIT command", m_quitSent);
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void onConnected() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("220 test.vmime.org Service ready\r\n");
|
|
|
|
processCommand();
|
|
|
|
|
|
|
|
m_state = STATE_COMMAND;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void onDataReceived() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
processCommand();
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void processCommand() {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
vmime::string line;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (!localReceiveLine(line)) {
|
2013-06-24 13:32:40 +00:00
|
|
|
return;
|
2018-09-05 21:54:48 +00:00
|
|
|
}
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
std::istringstream iss(line);
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
switch (m_state) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
case STATE_NOT_CONNECTED:
|
|
|
|
|
|
|
|
localSend("451 Requested action aborted: invalid state\r\n");
|
|
|
|
break;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
case STATE_COMMAND: {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
std::string cmd;
|
|
|
|
iss >> cmd;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (cmd == "EHLO") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("250-test.vmime.org says hello\r\n");
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (WITH_CHUNKING) {
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("250-CHUNKING\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
}
|
2013-06-24 13:32:40 +00:00
|
|
|
|
|
|
|
localSend("250 SIZE 1000000\r\n");
|
|
|
|
|
|
|
|
m_ehloSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "HELO") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("Client must not send the HELO command, as EHLO succeeded", false);
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "MAIL") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("The MAIL command must be sent only one time", !m_mailSent);
|
|
|
|
|
|
|
|
std::string address;
|
|
|
|
iss >> address;
|
|
|
|
|
|
|
|
VASSERT_EQ("MAIL/address", "FROM:<expeditor@test.vmime.org>", address);
|
|
|
|
|
|
|
|
std::string option;
|
|
|
|
iss >> option;
|
|
|
|
|
|
|
|
VASSERT_EQ("MAIL/size", "SIZE=4194304", option);
|
|
|
|
|
|
|
|
localSend("552 Channel size limit exceeded\r\n");
|
|
|
|
|
|
|
|
m_mailSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "NOOP") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("250 Completed\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "QUIT") {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
localSend("221 test.vmime.org Service closing transmission channel\r\n");
|
|
|
|
|
|
|
|
m_quitSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
VASSERT("No other command should be sent", false);
|
|
|
|
|
|
|
|
localSend("502 Command not implemented\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
processCommand();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
enum State {
|
2013-06-24 13:32:40 +00:00
|
|
|
STATE_NOT_CONNECTED,
|
|
|
|
STATE_COMMAND,
|
|
|
|
STATE_DATA
|
|
|
|
};
|
|
|
|
|
|
|
|
int m_state;
|
|
|
|
|
|
|
|
bool m_ehloSent, m_mailSent, m_rcptSent, m_quitSent;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <unsigned long SIZE>
|
2018-09-05 21:54:48 +00:00
|
|
|
class SMTPBigTestMessage : public vmime::message {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
public:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
size_t getGeneratedSize(const vmime::generationContext& /* ctx */) {
|
|
|
|
|
2013-06-24 13:32:40 +00:00
|
|
|
return SIZE;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void generateImpl(
|
|
|
|
const vmime::generationContext& /* ctx */,
|
|
|
|
vmime::utility::outputStream& outputStream,
|
|
|
|
const vmime::size_t /* curLinePos */ = 0,
|
|
|
|
vmime::size_t* /* newLinePos */ = NULL
|
|
|
|
) const {
|
|
|
|
|
|
|
|
for (unsigned int i = 0, n = SIZE ; i < n ; ++i) {
|
2013-06-24 13:32:40 +00:00
|
|
|
outputStream.write("X", 1);
|
2018-09-05 21:54:48 +00:00
|
|
|
}
|
2013-06-24 13:32:40 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef SMTPBigTestMessage <4194304> SMTPBigTestMessage4MB;
|
2017-12-14 20:39:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** SMTP test server for SMTPUTF8 extension.
|
|
|
|
*/
|
|
|
|
template <bool SUPPORTS_UTF8>
|
2018-09-05 21:54:48 +00:00
|
|
|
class UTF8SMTPTestSocket : public lineBasedTestSocket {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
public:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
UTF8SMTPTestSocket() {
|
|
|
|
|
|
|
|
if (SUPPORTS_UTF8) {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
m_rcptLines.insert("RCPT TO:<recipient1@test.vmime.org>");
|
|
|
|
m_rcptLines.insert("RCPT TO:<recipient2@test.vmime.org>");
|
2017-12-14 21:11:58 +00:00
|
|
|
m_rcptLines.insert("RCPT TO:<récepteur@test.vmime.org>");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
m_rcptLines.insert("RCPT TO:<recipient1@test.vmime.org>");
|
|
|
|
m_rcptLines.insert("RCPT TO:<recipient2@test.vmime.org>");
|
|
|
|
m_rcptLines.insert("RCPT TO:<=?utf-8?Q?r=C3=A9cepteur?=@test.vmime.org>");
|
|
|
|
}
|
|
|
|
|
|
|
|
m_state = STATE_NOT_CONNECTED;
|
|
|
|
m_ehloSent = m_mailSent = m_rcptSent = m_dataSent = m_quitSent = false;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
~UTF8SMTPTestSocket() {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void onConnected() {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
localSend("220 test.vmime.org Service ready\r\n");
|
|
|
|
processCommand();
|
|
|
|
|
|
|
|
m_state = STATE_COMMAND;
|
|
|
|
}
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
void processCommand() {
|
|
|
|
|
|
|
|
if (!haveMoreLines()) {
|
2017-12-14 20:39:29 +00:00
|
|
|
return;
|
2018-09-05 21:54:48 +00:00
|
|
|
}
|
2017-12-14 20:39:29 +00:00
|
|
|
|
|
|
|
vmime::string line = getNextLine();
|
|
|
|
std::istringstream iss(line);
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
switch (m_state) {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
case STATE_NOT_CONNECTED:
|
|
|
|
|
|
|
|
localSend("451 Requested action aborted: invalid state\r\n");
|
|
|
|
break;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
case STATE_COMMAND: {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
std::string cmd;
|
|
|
|
iss >> cmd;
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (cmd.empty()) {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
localSend("500 Syntax error, command unrecognized\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "EHLO") {
|
|
|
|
|
|
|
|
if (SUPPORTS_UTF8) {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
localSend("250-test.vmime.org\r\n");
|
|
|
|
localSend("250 SMTPUTF8\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
localSend("250 test.vmime.org\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ehloSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "HELO") {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
VASSERT("Client must not send the HELO command, as EHLO succeeded", false);
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "MAIL") {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
VASSERT("Client must send the EHLO command", m_ehloSent);
|
|
|
|
VASSERT("The MAIL command must be sent only one time", !m_mailSent);
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
if (SUPPORTS_UTF8) {
|
|
|
|
|
2017-12-14 21:11:58 +00:00
|
|
|
VASSERT(
|
|
|
|
"MAIL",
|
|
|
|
std::string("MAIL FROM:<expediteur@test.vmime.org> SMTPUTF8") == line
|
|
|
|
|| std::string("MAIL FROM:<expéditeur@test.vmime.org> SMTPUTF8") == line
|
|
|
|
);
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2017-12-14 21:11:58 +00:00
|
|
|
VASSERT(
|
|
|
|
"MAIL",
|
|
|
|
std::string("MAIL FROM:<expediteur@test.vmime.org>") == line
|
|
|
|
|| std::string("MAIL FROM:<=?utf-8?Q?exp=C3=A9diteur?=@test.vmime.org>") == line
|
|
|
|
);
|
2017-12-14 20:39:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
localSend("250 OK\r\n");
|
|
|
|
|
|
|
|
m_mailSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "RCPT") {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
std::set <vmime::string>::iterator it = m_rcptLines.find(line);
|
|
|
|
|
|
|
|
VASSERT(std::string("RCPT not found: '") + line + "'", it != m_rcptLines.end());
|
|
|
|
|
|
|
|
m_rcptLines.erase(it);
|
|
|
|
|
|
|
|
localSend("250 OK, recipient accepted\r\n");
|
|
|
|
|
|
|
|
m_rcptSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "DATA") {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
VASSERT("Client must send the MAIL command", m_mailSent);
|
|
|
|
VASSERT("Client must send the RCPT command", m_rcptSent);
|
|
|
|
VASSERT("All recipients", m_rcptLines.empty());
|
|
|
|
|
|
|
|
localSend("354 Ready to accept data; end with <CRLF>.<CRLF>\r\n");
|
|
|
|
|
|
|
|
m_state = STATE_DATA;
|
|
|
|
m_msgData.clear();
|
|
|
|
|
|
|
|
m_dataSent = true;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "NOOP") {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
localSend("250 Completed\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else if (cmd == "QUIT") {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
m_quitSent = true;
|
|
|
|
|
|
|
|
localSend("221 test.vmime.org Service closing transmission channel\r\n");
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
localSend("502 Command not implemented\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2018-09-05 21:54:48 +00:00
|
|
|
case STATE_DATA: {
|
|
|
|
|
|
|
|
if (line == ".") {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
VASSERT_EQ("Data", "Message data\r\n", m_msgData);
|
|
|
|
|
|
|
|
localSend("250 Message accepted for delivery\r\n");
|
|
|
|
m_state = STATE_COMMAND;
|
2018-09-05 21:54:48 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2017-12-14 20:39:29 +00:00
|
|
|
m_msgData += line + "\r\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
processCommand();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
2018-09-05 21:54:48 +00:00
|
|
|
enum State {
|
2017-12-14 20:39:29 +00:00
|
|
|
STATE_NOT_CONNECTED,
|
|
|
|
STATE_COMMAND,
|
|
|
|
STATE_DATA
|
|
|
|
};
|
|
|
|
|
|
|
|
int m_state;
|
|
|
|
|
|
|
|
std::set <vmime::string> m_rcptLines;
|
|
|
|
|
|
|
|
std::string m_msgData;
|
|
|
|
|
|
|
|
bool m_ehloSent, m_mailSent, m_rcptSent, m_dataSent, m_quitSent;
|
|
|
|
};
|