diff options
Diffstat (limited to 'src/messaging/POP3Store.cpp')
-rw-r--r-- | src/messaging/POP3Store.cpp | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/src/messaging/POP3Store.cpp b/src/messaging/POP3Store.cpp new file mode 100644 index 00000000..c105bc4b --- /dev/null +++ b/src/messaging/POP3Store.cpp @@ -0,0 +1,603 @@ +// +// VMime library (http://vmime.sourceforge.net) +// Copyright (C) 2002-2004 Vincent Richard <[email protected]> +// +// 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "POP3Store.hpp" + +#include "../exception.hpp" +#include "../platformDependant.hpp" +#include "../messageId.hpp" +#include "../utility/md5.hpp" + +#include "POP3Folder.hpp" + +#include <algorithm> + + +namespace vmime { +namespace messaging { + + +POP3Store::POP3Store(class session& sess, class authenticator* auth) + : store(sess, infosInstance(), auth), m_socket(NULL), + m_authentified(false), m_timeoutHandler(NULL) +{ +} + + +POP3Store::~POP3Store() +{ + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); +} + + +const string POP3Store::protocolName() const +{ + return "pop3"; +} + + +folder* POP3Store::getDefaultFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new POP3Folder(folder::path(folder::path::component("INBOX")), this); +} + + +folder* POP3Store::getRootFolder() +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new POP3Folder(folder::path(), this); +} + + +folder* POP3Store::getFolder(const folder::path& path) +{ + if (!isConnected()) + throw exceptions::illegal_state("Not connected"); + + return new POP3Folder(path, this); +} + + +void POP3Store::connect() +{ + if (isConnected()) + throw exceptions::already_connected(); + + const string address = session().properties()[sm_infos.propertyPrefix() + "server.address"]; + const port_t port = session().properties().get(sm_infos.propertyPrefix() + "server.port", sm_infos.defaultPort()); + + // Create the time-out handler + if (session().properties().exists + (sm_infos.propertyPrefix() + "timeout.factory")) + { + timeoutHandlerFactory* tof = platformDependant::getHandler()-> + getTimeoutHandlerFactory(session().properties() + [sm_infos.propertyPrefix() + "timeout.factory"]); + + m_timeoutHandler = tof->create(); + } + + // Create and connect the socket + socketFactory* sf = platformDependant::getHandler()->getSocketFactory + (session().properties().get(sm_infos.propertyPrefix() + "server.socket-factory", string("default"))); + + m_socket = sf->create(); + m_socket->connect(address, port); + + // Connection + // + // eg: C: <connection to server> + // --- S: +OK MailSite POP3 Server 5.3.4.0 Ready <[email protected]> + + string response; + readResponse(response, false); + + if (isSuccessResponse(response)) + { + bool authentified = false; + + const authenticationInfos auth = authenticator().requestAuthInfos(); + + // Secured authentication with APOP (if requested and if available) + // + // eg: C: APOP vincent <digest> + // --- S: +OK vincent is a valid mailbox + messageId mid(response); + + if (session().properties().get(sm_infos.propertyPrefix() + "options.apop", false)) + { + if (mid.left().length() && mid.right().length()) + { + // <digest> is the result of MD5 applied to "<message-id>password" + sendRequest("APOP " + auth.username() + " " + + utility::md5(mid.generate() + auth.password()).hex()); + readResponse(response, false); + + if (isSuccessResponse(response)) + { + authentified = true; + } + else + { + if (session().properties().get(sm_infos.propertyPrefix() + + "options.apop.fallback", false) == false) + { + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + } + else + { + // APOP not supported + if (session().properties().get(sm_infos.propertyPrefix() + + "options.apop.fallback", false) == false) + { + // Can't fallback on basic authentification + internalDisconnect(); + throw exceptions::unsupported_option(); + } + } + } + + if (!authentified) + { + // Basic authentication + // + // eg: C: USER vincent + // --- S: +OK vincent is a valid mailbox + // + // C: PASS couic + // S: +OK vincent's maildrop has 2 messages (320 octets) + + sendRequest("USER " + auth.username()); + readResponse(response, false); + + if (isSuccessResponse(response)) + { + sendRequest("PASS " + auth.password()); + readResponse(response, false); + + if (!isSuccessResponse(response)) + { + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + else + { + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + } + else + { + internalDisconnect(); + throw exceptions::connection_greeting_error(response); + } + + m_authentified = true; +} + + +const bool POP3Store::isConnected() const +{ + return (m_socket && m_socket->isConnected() && m_authentified); +} + + +void POP3Store::disconnect() +{ + if (!isConnected()) + throw exceptions::not_connected(); + + internalDisconnect(); +} + + +void POP3Store::internalDisconnect() +{ + for (std::list <POP3Folder*>::iterator it = m_folders.begin() ; + it != m_folders.end() ; ++it) + { + (*it)->onStoreDisconnected(); + } + + m_folders.clear(); + + + sendRequest("QUIT"); + + m_socket->disconnect(); + + delete (m_socket); + m_socket = NULL; + + delete (m_timeoutHandler); + m_timeoutHandler = NULL; + + m_authentified = false; +} + + +void POP3Store::noop() +{ + m_socket->send("NOOP"); + + string response; + readResponse(response, false); + + if (!isSuccessResponse(response)) + throw exceptions::command_error("NOOP", response); +} + + +const bool POP3Store::isSuccessResponse(const string& buffer) +{ + static const string OK("+OK"); + + return (buffer.length() >= 3 && + std::equal(buffer.begin(), buffer.begin() + 3, OK.begin())); +} + + +const bool POP3Store::stripFirstLine(const string& buffer, string& result, string* firstLine) +{ + const string::size_type end = buffer.find('\n'); + + if (end != string::npos) + { + if (firstLine) *firstLine = buffer.substr(0, end); + result = buffer.substr(end + 1); + return (true); + } + else + { + result = buffer; + return (false); + } +} + + +void POP3Store::stripResponseCode(const string& buffer, string& result) +{ + const string::size_type pos = buffer.find_first_of(" \t"); + + if (pos != string::npos) + result = buffer.substr(pos + 1); + else + result = buffer; +} + + +void POP3Store::sendRequest(const string& buffer, const bool end) +{ + m_socket->send(buffer); + if (end) m_socket->send("\r\n"); +} + + +void POP3Store::readResponse(string& buffer, const bool multiLine, + progressionListener* progress) +{ + bool foundTerminator = false; + int current = 0, total = 0; + + if (progress) + progress->start(total); + + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + buffer.clear(); + + string::value_type last1 = '\0', last2 = '\0'; + + for ( ; !foundTerminator ; ) + { +#if 0 // not supported + // Check for possible cancellation + if (progress && progress->cancel()) + throw exceptions::operation_cancelled(); +#endif + + // Check whether the time-out delay is elapsed + if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + } + + // Receive data from the socket + string receiveBuffer; + m_socket->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platformDependant::getHandler()->wait(); + continue; + } + + // We have received data: reset the time-out counter + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + // Check for transparent characters: '\n..' becomes '\n.' + const string::value_type first = receiveBuffer[0]; + + if (first == '.' && last2 == '\n' && last1 == '.') + { + receiveBuffer.erase(receiveBuffer.begin()); + } + else if (receiveBuffer.length() >= 2 && first == '.' && + receiveBuffer[1] == '.' && last1 == '\n') + { + receiveBuffer.erase(receiveBuffer.begin()); + } + + for (string::size_type trans ; + string::npos != (trans = receiveBuffer.find("\n..")) ; ) + { + receiveBuffer.replace(trans, 3, "\n."); + } + + last1 = receiveBuffer[receiveBuffer.length() - 1]; + last2 = (receiveBuffer.length() >= 2) ? receiveBuffer[receiveBuffer.length() - 2] : 0; + + // Append the data to the response buffer + buffer += receiveBuffer; + current += receiveBuffer.length(); + + // Check for terminator string (and strip it if present) + foundTerminator = checkTerminator(buffer, multiLine); + + // Notify progression + if (progress) + { + total = std::max(total, current); + progress->progress(current, total); + } + + // If there is an error (-ERR) when executing a command that + // requires a multi-line response, the error response will + // include only one line, so we stop waiting for a multi-line + // terminator and check for a "normal" one. + if (multiLine && !foundTerminator && buffer.length() >= 4 && buffer[0] == '-') + { + foundTerminator = checkTerminator(buffer, false); + } + } + + if (progress) + progress->stop(total); +} + + +void POP3Store::readResponse(utility::outputStream& os, progressionListener* progress, + const int predictedSize) +{ + bool foundTerminator = false; + int current = 0, total = predictedSize; + + string temp; + bool codeDone = false; + + if (progress) + progress->start(total); + + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + string::value_type last1 = '\0', last2 = '\0'; + + for ( ; !foundTerminator ; ) + { +#if 0 // not supported + // Check for possible cancellation + if (progress && progress->cancel()) + throw exceptions::operation_cancelled(); +#endif + + // Check whether the time-out delay is elapsed + if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + } + + // Receive data from the socket + string receiveBuffer; + m_socket->receive(receiveBuffer); + + if (receiveBuffer.empty()) // buffer is empty + { + platformDependant::getHandler()->wait(); + continue; + } + + // We have received data: reset the time-out counter + if (m_timeoutHandler) + m_timeoutHandler->resetTimeOut(); + + // Check for transparent characters: '\n..' becomes '\n.' + const string::value_type first = receiveBuffer[0]; + + if (first == '.' && last2 == '\n' && last1 == '.') + { + receiveBuffer.erase(receiveBuffer.begin()); + } + else if (receiveBuffer.length() >= 2 && first == '.' && + receiveBuffer[1] == '.' && last1 == '\n') + { + receiveBuffer.erase(receiveBuffer.begin()); + } + + for (string::size_type trans ; + string::npos != (trans = receiveBuffer.find("\n..")) ; ) + { + receiveBuffer.replace(trans, 3, "\n."); + } + + last1 = receiveBuffer[receiveBuffer.length() - 1]; + last2 = (receiveBuffer.length() >= 2) ? receiveBuffer[receiveBuffer.length() - 2] : 0; + + // If we don't have extracted the response code yet + if (!codeDone) + { + temp += receiveBuffer; + + string firstLine; + + if (stripFirstLine(temp, temp, &firstLine) == true) + { + if (!isSuccessResponse(firstLine)) + throw exceptions::command_error("?", firstLine); + + receiveBuffer = temp; + temp.clear(); + + codeDone = true; + } + } + + if (codeDone) + { + // Check for terminator string (and strip it if present) + foundTerminator = checkTerminator(receiveBuffer, true); + + // Inject the data into the output stream + os.write(receiveBuffer.data(), receiveBuffer.length()); + current += receiveBuffer.length(); + + // Notify progression + if (progress) + { + total = std::max(total, current); + progress->progress(current, total); + } + } + } + + if (progress) + progress->stop(total); +} + + +const bool POP3Store::checkTerminator(string& buffer, const bool multiLine) +{ + // Multi-line response + if (multiLine) + { + static const string term1("\r\n.\r\n"); + static const string term2("\n.\n"); + + return (checkOneTerminator(buffer, term1) || + checkOneTerminator(buffer, term2)); + } + // Normal response + else + { + static const string term1("\r\n"); + static const string term2("\n"); + + return (checkOneTerminator(buffer, term1) || + checkOneTerminator(buffer, term2)); + } + + return (false); +} + + +const bool POP3Store::checkOneTerminator(string& buffer, const string& term) +{ + if (buffer.length() >= term.length() && + std::equal(buffer.end() - term.length(), buffer.end(), term.begin())) + { + buffer.erase(buffer.end() - term.length(), buffer.end()); + return (true); + } + + return (false); +} + + +void POP3Store::registerFolder(POP3Folder* folder) +{ + m_folders.push_back(folder); +} + + +void POP3Store::unregisterFolder(POP3Folder* folder) +{ + std::list <POP3Folder*>::iterator it = std::find(m_folders.begin(), m_folders.end(), folder); + if (it != m_folders.end()) m_folders.erase(it); +} + + + +// Service infos + +POP3Store::_infos POP3Store::sm_infos; + + +const port_t POP3Store::_infos::defaultPort() const +{ + return (110); +} + + +const string POP3Store::_infos::propertyPrefix() const +{ + return "store.pop3."; +} + + +const std::vector <string> POP3Store::_infos::availableProperties() const +{ + std::vector <string> list; + + // POP3-specific options + list.push_back("options.apop"); + list.push_back("options.apop.fallback"); + + // Common properties + list.push_back("auth.username"); + list.push_back("auth.password"); + + list.push_back("server.address"); + list.push_back("server.port"); + list.push_back("server.socket-factory"); + + list.push_back("timeout.factory"); + + return (list); +} + + +} // messaging +} // vmime |