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