// // File: hzPop3.cpp // // Legal Notice: This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com) // // The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free // Software Foundation, either version 3 of the License, or any later version. // // The HadronZoo C++ Class Library 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses. //
// // Method: Contacts a POP3 server and retrieves emails. //
#include <iostream> #include <fstream>
#include <sys/stat.h>
#include <unistd.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/socket.h> #include <openssl/ssl.h> #include <pthread.h>
#include "hzTextproc.h" #include "hzDirectory.h" #include "hzIpServer.h" #include "hzTcpClient.h" #include "hzProcess.h" #include "hzCodec.h" #include "hzMailer.h"
using namespace std ;
/* ** Functions */
hzEcode hzPop3Acc::Init (hzString Server, hzString Username, hzString Password, hzString Repos) { // Initialize a POP3 Account that our application will access as client. // // Arguments: 1) Server Server/Hostname to connect to. // 2) Username Username for the account. // 3) Password Password for the account. // 4) Repos Pathname for the email repository (of downloaded messages and headers) // // Returns: E_INITDUP If this POP3 account manager has already been initialized // E_ARGUMENT If the server, username, password or the email repository pathname are not supplied // E_OK If this POP3 account manager is now initialized
hzVect <hzString> dirs ; // Vector of directories populated by ListDir() hzVect <hzString> files ; // Vector of files populated by ListDir()
hzString S ; // Temp string (filename) uint32_t nIndex ; // Files iterator hzEcode rc ; // Return code
m_Error.Clear() ;
m_Server = Server ; m_Username = Username ; m_Password = Password ; m_Repos = Repos ;
if (m_Repos) { m_Error.Printf("hzPop3Acc::Init - Duplicate call\n") ; return E_INITDUP ; }
if (!m_Server || !m_Username || !m_Password || !m_Repos) { m_Error.Printf("hzPop3Acc::Init\n") ;
if (!m_Server) m_Error.Printf(" - No POP3 server specified\n") ; if (!m_Username) m_Error.Printf(" - No POP3 Username\n") ; if (!m_Password) m_Error.Printf(" - No POP3 Password\n") ; if (!m_Repos) m_Error.Printf(" - No POP3 Repository specified\n") ;
return E_ARGUMENT ; }
rc = AssertDir(Repos, 0777) ; if (rc != E_OK) return rc ;
// Read the repository and populate the m_Already map rc = ListDir(dirs, files, *Repos, 0) ; for (nIndex = 0 ; nIndex < files.Count() ; nIndex++) { S = files[nIndex] ; m_Already.Insert(S) ; }
return E_OK ; }
hzEcode hzPop3Acc::Collect (hzVect<hzString>& messages) { // Collect emails from the POP3 Account (Conduct a POP3 session with the designated server). The emails are each placed in thier own file. // // Arguments: 1) messages A vector of strings that is populated with filename of collected emails. // // Returns: E_NOINIT The POP3 account is not initialized // E_PROTOCOL The session message not as expected // E_OK The email collection was successful
_hzfunc("hzPop3Acc::Collect") ;
hzMapS<int32_t,hzString> temp ; // Emails available from the POP3 server
ofstream os ; // Output stream hzTcpClient P ; // POP3 client connection chIter zi ; // For iterating server responses hzChain Z ; // For sending commands and receiving responses hzChain word ; // For capturing small strings (eg mail number and mail id) hzChain msg ; // For garnering messages hzString S ; // For conversion of 'word' into a string hzString path ; // For email filenames uint32_t mailId ; // Mail number as given by server uint32_t nIndex ; // For iterating avail emails hzEcode rc ; // Return code
m_Error.Clear() ;
if (!m_Repos) { threadLog("POP3 Account not initialized\n") ; return E_NOINIT ; }
rc = P.ConnectStd(m_Server, 110) ; if (rc != E_OK) { threadLog("Cannot connect to email server %s (error=%s)\n", *m_Server, Err2Txt(rc)) ; return rc ; }
rc = P.SetSendTimeout(30) ; if (rc != E_OK) { threadLog("Could not set send_timeout on connection to POP3 server (error=%s)\n", Err2Txt(rc)) ; return rc ; }
rc = P.SetRecvTimeout(30) ; if (rc != E_OK) { threadLog("Could not set recv_timeout on connection to POP3 server (error=%s)\n", Err2Txt(rc)) ; return rc ; }
// Expect the server to talk first with a +OK Z.Clear() ; rc = P.Recv(Z) ; if (rc != E_OK) { threadLog("Cannot recv server hello (error=%s)\n", Err2Txt(rc)) ; goto done ; } S = Z ; threadLog("Server: [%s]\n", *S) ;
zi = Z ; if (zi != "+OK") { rc = E_PROTOCOL ; threadLog("Expected +OK as hello from server. (error=%s)\n", Err2Txt(rc)) ; goto done ; }
// Send the initial username Z.Clear() ; Z.Printf("USER %s\r\n", *m_Username) ;
S = Z ; threadLog("Client: [%s]\n", *S) ;
rc = P.Send(Z) ; if (rc != E_OK) { threadLog("Cannot send username to email server (error=%s)\n", Err2Txt(rc)) ; goto done ; }
// Recv the +OK\r\n (to the username) Z.Clear() ; rc = P.Recv(Z) ; if (rc != E_OK) { threadLog("Cannot recv response to username from email server (error=%s)\n", Err2Txt(rc)) ; goto done ; } S = Z ; threadLog("Server: [%s]\n", *S) ;
zi = Z ; if (zi != "+OK") { rc = E_PROTOCOL ; S = Z ; threadLog("Expected +OK response to username (got=%s)\n", *S) ; goto done ; }
// Send the password Z.Clear() ; Z.Printf("PASS %s\r\n", *m_Password) ;
S = Z ; threadLog("Client: [%s]\n", *S) ;
rc = P.Send(Z) ; if (rc != E_OK) { threadLog("Cannot send password to email server (error=%s)\n", Err2Txt(rc)) ; rc = E_PROTOCOL ; goto done ; }
// Recv the +OK\r\n (to the password) Z.Clear() ; rc = P.Recv(Z) ; if (rc != E_OK) { threadLog("Cannot recv response to username from email server (error=%s)\n", Err2Txt(rc)) ; goto done ; } S = Z ; threadLog("Server: [%s]\n", *S) ;
zi = Z ; if (zi != "+OK") { S = Z ; threadLog("Expected +OK to password. (got=%s)\n", *S) ; rc = E_PROTOCOL ; goto done ; }
// Send the UIDL command Z.Clear() ; Z.Printf("UIDL\r\n") ;
S = Z ; threadLog("Client: [%s]\n", *S) ;
rc = P.Send(Z) ; if (rc != E_OK) { threadLog("Cannot send UIDL command to email server (error=%s)\n", Err2Txt(rc)) ; goto done ; }
// Recv the +OK\r\n (to the UIDL command) Z.Clear() ; for (;;) //nIndex = 0 ; nIndex < 3 ; nIndex++) { rc = P.Recv(Z) ; if (rc != E_OK) { threadLog("Cannot recv response to UIDL from email server (error=%s)\n", Err2Txt(rc)) ; goto done ; }
threadLog("Server - Response to UIDL of %d bytes\n", Z.Size()) ;
// Test for the \r\n.\r\n zi = Z ; zi += (Z.Size() - 5) ;
if (zi == "\r\n.\r\n") break ; } S = Z ; threadLog("Server: [%s]\n", *S) ;
zi = Z ; if (zi != "+OK") { rc = E_PROTOCOL ; threadLog("Expected +OK response to UIDL (error=%s)\n", Err2Txt(rc)) ; goto done ; }
// Recieve the list of available messages and place them in the temporary map for (; !zi.eof() && *zi != CHAR_NL ; zi++) ; zi++ ;
for (; !zi.eof() ; zi++) { for (; !zi.eof() && IsDigit(*zi) ; zi++) word.AddByte(*zi) ;
if (!word.Size()) break ;
//zi.Skipwhite() ; S = word ; mailId = atoi(*S) ; word.Clear() ;
for (zi++ ; !zi.eof() ; zi++) { if (*zi == CHAR_CR) zi++ ; if (*zi == CHAR_NL) break ;
word.AddByte(*zi) ; }
S = word ; word.Clear() ; if (!m_Already.Exists(S)) temp.Insert(mailId, S) ;
if (zi.eof()) break ; }
for (nIndex = 0 ; nIndex < temp.Count() ; nIndex++) { mailId = temp.GetKey(nIndex) ; S = temp.GetObj(nIndex) ;
threadLog("Mailbox has %d %d (%s)\n", nIndex, mailId, *S) ; }
// Recv the email list. Some of these we may already have for (nIndex = 0 ; rc == E_OK && nIndex < temp.Count() ; nIndex++) { mailId = temp.GetKey(nIndex) ; S = temp.GetObj(nIndex) ;
// Send the RETR command Z.Clear() ; Z.Printf("RETR %d\r\n", mailId) ; rc = P.Send(Z) ; if (rc != E_OK) { threadLog("Cannot send RETR command to email server\n") ; break ; }
// Recv the +OK (to the RETR command). This involves repeated calls to Recv until the chain ends with a \r\n.\r\n sequence. Z.Clear() ;
for (;;) { rc = P.Recv(Z) ; if (rc != E_OK) { threadLog("Cannot recv response to RETR command\n") ; break ; }
threadLog("Server - Message of %d bytes\n", Z.Size()) ;
// Test for the \r\n.\r\n zi = Z ; zi += (Z.Size() - 5) ;
if (zi == "\r\n.\r\n") break ; }
// Go to end of line, then message body, the the CR/NL period CR/NL sequence msg.Clear() ; zi = Z ; if (zi != "+OK") { rc = E_OK ; S = Z ; threadLog("Expected +OK in response to RETR command (got=%s)\n", *S) ; continue ; } for (zi += 3 ; !zi.eof() && *zi != CHAR_NL ; zi++) ; if (*zi != CHAR_NL) { threadLog("Malformed +OK response to RETR command\n") ; continue ; }
for (zi++ ; !zi.eof() ; zi++) { if (*zi == CHAR_CR) { if (zi == "\r\n.\r\n") { zi += 5 ; if (zi.eof()) break ; zi -= 5 ; } }
msg.AddByte(*zi) ; }
path = m_Repos + "/" + S ; os.open(*path) ; if (os.fail()) threadLog("Cannot open file %s for writting\n", *path) ; else { threadLog("Writing message file %s\n", *path) ; os << msg ; os.close() ; os.clear() ; messages.Add(S) ; } }
os.close() ;
done: P.Close() ; return rc ; }
hzEcode hzPop3Acc::GetEmail (hzEmail& theMessage, hzString& mailId) { // Go to the archive file for emails (one produced each day), and retrieve the email header and body for the given id // // Arguments: 1) em The email instance populated by this operation // 2) mailId The email unique id // // Returns: E_OPENFAIL If the mailbox file could not be opened. // E_FORMAT If the email is malformed // E_OK If the email was successfully retrieved from the mailbox
_hzfunc("hzPop3Acc::GetEmail") ;
hzList<hzEmpart>::Iter pi ; // Iterator for the email parts
ifstream is ; // Input stream hzChain Z ; // For loading message hzString filepath ; // Full path to email file hzEcode rc ; // Return code
m_Error.Clear() ; theMessage.Clear() ;
// Open email file and read in the header. This starts with a line of the form 'From email_addr date' and is followed by lines of // the form 'param: args'. These lines may be followed by continuation lines that begin with whitespace. We read in continuation // lines as though they are just extensions to the preceeding parameter line.
filepath = m_Repos + "/" + mailId ;
rc = OpenInputStrm(is, filepath) ; if (rc != E_OK) { hzerr(E_OPENFAIL, "Failed tp open email file %s", *filepath) ; return E_OPENFAIL ; } Z << is ; is.close() ;
rc = theMessage.Import(Z) ; return rc ; }