// // File: hzMailer.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. //
// // Implimentation of automatic emailing from within an application. //
#include <cstdio> #include <iostream> #include <string> #include <fstream>
#include "hzChars.h" #include "hzTextproc.h" #include "hzMimetype.h" #include "hzTcpClient.h" #include "hzIpServer.h" #include "hzCodec.h" #include "hzDNS.h" #include "hzDirectory.h" #include "hzMailer.h" #include "hzProcess.h"
using namespace std ;
/* ** For Future improvement of IMF processing */
global hzSet<hzString> _hzGlobal_EmsgHdrs ;
void HadronZooInitMessageHdrs (void) { // Category: Data Initialization // // List of possible email message headers as supplied by IANA. Note the following denotations E-experimental, S-Standard and O-Obsolete
if (_hzGlobal_EmsgHdrs.Count()) return ;
hzString S ;
S = "X-Epistula-Ingress" ; _hzGlobal_EmsgHdrs.Insert(S) ; // HadronZoo::Epistula Specific S = "X-Epistula-ServerID" ; _hzGlobal_EmsgHdrs.Insert(S) ; // HadronZoo::Epistula Specific S = "Accept-Language" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Alternate-Recipient" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "ARC-Authentication-Results" ; _hzGlobal_EmsgHdrs.Insert(S) ; // E [RFC8617] S = "ARC-Message-Signature" ; _hzGlobal_EmsgHdrs.Insert(S) ; // E [RFC8617] S = "ARC-Seal" ; _hzGlobal_EmsgHdrs.Insert(S) ; // E [RFC8617] S = "Archived-At" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5064] S = "Authentication-Results " ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC8601] S = "Auto-Submitted" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC3834 section 5] S = "Autoforwarded" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Autosubmitted" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Bcc" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Cc" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Comments" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Content-Identifier " ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Content-Return" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Conversion" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Conversion-With-Loss" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "DL-Expansion-History" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Date" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Deferred-Delivery" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Delivery-Date" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Discarded-X400-IPMS-Extensions" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Discarded-X400-MTS-Extensions" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Disclose-Recipients" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Disposition-Notification-Options" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Disposition-Notification-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "DKIM-Signature" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC6376] S = "Downgraded-Bcc" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Cc" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Disposition-Notification-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Final-Recipient" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC6857 Section 3.1.10] S = "Downgraded-From" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857 Section 3.1.10] S = "Downgraded-In-Reply-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC6857 Section 3.1.10] S = "Downgraded-Mail-From" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857 Section 3.1.10] S = "Downgraded-Message-Id" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC6857 Section 3.1.10] S = "Downgraded-Original-Recipient" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC6857 Section 3.1.10] S = "Downgraded-Rcpt-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-References" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC6857 Section 3.1.10] S = "Downgraded-Reply-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Resent-Bcc" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Resent-Cc" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Resent-From" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Resent-Reply-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Resent-Sender" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Resent-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Return-Path" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-Sender" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Downgraded-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5504][RFC6857] S = "Encoding" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Encrypted" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Expires" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Expiry-Date" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "From" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322][RFC6854] S = "Generate-Delivery-Report" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Importance" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "In-Reply-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Incomplete-Copy" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Keywords" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Language" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Latest-Delivery-Time" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "List-Archive" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "List-Help" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "List-ID" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "List-Owner" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "List-Post" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "List-Subscribe" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "List-Unsubscribe" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "List-Unsubscribe-Post" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC8058] S = "Message-Context" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Message-ID" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Message-Type" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "MMHS-Exempted-Address" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.1 and Appendix B.105] S = "MMHS-Extended-Authorisation-Info" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.2 and Appendix B.106] S = "MMHS-Subject-Indicator-Codes" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.3 and Appendix B.107] S = "MMHS-Handling-Instructions" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.4 and Appendix B.108] S = "MMHS-Message-Instructions" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.5 and Appendix B.109] S = "MMHS-Codress-Message-Indicator" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.6 and Appendix B.110] S = "MMHS-Originator-Reference" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.7 and Appendix B.111] S = "MMHS-Primary-Precedence" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.8 and Appendix B.101] S = "MMHS-Copy-Precedence" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.9 and Appendix B.102] S = "MMHS-Message-Type" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.10 and Appendix B.103] S = "MMHS-Other-Recipients-Indicator-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.12 and Appendix B.113] S = "MMHS-Other-Recipients-Indicator-CC" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.12 and Appendix B.113] S = "MMHS-Acp127-Message-Identifier" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.14 and Appendix B.116] S = "MMHS-Originator-PLAD" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC6477][ACP123 Appendix A1.15 and Appendix B.117] S = "MT-Priority" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC6758] S = "Obsoletes" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Organization" ; _hzGlobal_EmsgHdrs.Insert(S) ; // informational [RFC7681] S = "Original-Encoded-Information-Types" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Original-From" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5703] S = "Original-Message-ID" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Original-Recipient" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC3798][RFC5337] S = "Originator-Return-Address" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Original-Subject" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5703] S = "PICS-Label" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Prevent-NonDelivery-Report" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Priority" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Received" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322][RFC5321] S = "Received-SPF" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC7208] S = "References" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Reply-By" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Reply-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Require-Recipient-Valid-Since" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC7293] S = "Resent-Bcc" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Resent-Cc" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Resent-Date" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Resent-From" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322][RFC6854] S = "Resent-Message-ID" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Resent-Reply-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // O [RFC5322] S = "Resent-Sender" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322][RFC6854] S = "Resent-To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Return-Path" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Sender" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322][RFC6854] S = "Sensitivity" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "Solicitation" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC3865] S = "Subject" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "Supersedes" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "TLS-Report-Domain" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC8460] S = "TLS-Report-Submitter" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC8460] S = "TLS-Required" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC8689] S = "To" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5322] S = "VBR-Info" ; _hzGlobal_EmsgHdrs.Insert(S) ; // S [RFC5518] S = "X400-Content-Identifier" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "X400-Content-Return" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "X400-Content-Type" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "X400-MTS-Identifier" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "X400-Originator" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "X400-Received" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "X400-Recipients" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] S = "X400-Trace" ; _hzGlobal_EmsgHdrs.Insert(S) ; // [RFC4021] }
/* ** SMTP Codes */
static SMTPCode _getSmtpCode (const char* cpBuf) { // Lift the SMTP return code from a SMTP server response // // Arguments: 1) ptr Pointer into buffer at point where the HTTP return code is expected. // // Returns: Enum STMP code
uint32_t smtpCode ; // SMTP return code
if (!cpBuf || !cpBuf[0]) return SMTP_INVALID ;
if (IsDigit(cpBuf[0]) && IsDigit(cpBuf[1]) && IsDigit(cpBuf[2]) && !IsDigit(cpBuf[3])) smtpCode = (100 * (cpBuf[0] - '0')) + (10 * (cpBuf[1] - '0')) + (cpBuf[2] - '0') ; else return SMTP_INVALID ;
return (SMTPCode) smtpCode ; }
const char* ReportErrorStatus (SMTPCode eSmtpCode) { // Category: Diagnostics // // Convert SMTP return code to textual equivelent for diagnostic purposes // // Arguments: 1) eSmtpCode The SMTP return code // // Returns: Pointer to SMTP return code description as cstr
static hzString smtp_code_text [] = { "0 SMTP return code indechicperable", "101 The server is unable to connect", "111 Connection refused or inability to open an SMTP stream", "200 Non standard system status message or help reply", "211 System status, or system help reply", "214 Help message (this reply is useful only to the human user)", "220 Service ready", "221 Service closing transmission channel", "235 Tells client to go ahead", "250 Requested mail action okay, completed", "251 User not local; will forward to", "252 Cannot VRFY user, but will accept message and attempt delivery", "334 Login info, either username or password", "354 Start mail input; end with <CRLF>.<CRLF>", "420 General connection timeout issue", "421 Service not available", "422 The recipients mailbox has exceeded its storage limit", "431 Not enough space on the disk - Expected to be temporary", "432 Recipients incoming mail queue has been stopped", "441 The recipients server is not responding", "442 The connection was dropped during the transmission.", "446 The maximum hop count was exceeded for the message", "447 Some SMTP servers will send this if they have decided to time out the client (for being too slow).", "449 Routing error", "450 Requested mail action not taken: mailbox unavailable (E.g., mailbox busy)", "451 Requested action aborted: local error in processing", "452 Action not taken: no space left", "471 Some SMTP servers return this in respose to errors on the part of the connecting client (as they see it).", "500 Syntax error, command unrecognized (This may include errors such as cmd line too long)", "501 Syntax error in parameters or arguments", "502 Command not implemented", "503 Bad sequence of commands", "504 Command parameter not implemented", "510 Bad email address", "511 Bad email address", "512 Host server for the recipients domain name cannot be found in DNS", "513 Address type is incorrect", "523 Size of your mail exceeds the server limits", "530 Authentication problem", "541 The recipient address rejected your message", "550 Non-existent email address", "551 User not local or address invalid.", "552 Requested mail action aborted: exceeded storage allocation", "553 Mailbox name not allowed (E.g., mailbox syntax incorrect)", "554 Transaction failed (Or in the case of a connection-opening response, No SMTP service)", "999 Not a 3 digit entity" } ;
switch (eSmtpCode) { case SMTP_NOOP: return *smtp_code_text[0] ; case SMTP_CONN_FAIL: return *smtp_code_text[1] ; case SMTP_CONN_REFUSED: return *smtp_code_text[2] ; case SMTP_STATUS_NS: return *smtp_code_text[3] ; case SMTP_STATUS: return *smtp_code_text[4] ; case SMTP_HELP: return *smtp_code_text[5] ; case SMTP_READY: return *smtp_code_text[6] ; case SMTP_QUIT: return *smtp_code_text[7] ; case SMTP_GO_AHEAD: return *smtp_code_text[8] ; case SMTP_OK: return *smtp_code_text[9] ; case SMTP_NOTLOCAL: return *smtp_code_text[10] ; case SMTP_NOVRFY: return *smtp_code_text[11] ; case SMTP_LOGINFO: return *smtp_code_text[12] ; case SMTP_SENDDATA: return *smtp_code_text[13] ; case SMTP_TIMEOUT: return *smtp_code_text[14] ; case SMTP_UNAVAILABLE: return *smtp_code_text[15] ; case SMTP_MBOX_FULL: return *smtp_code_text[16] ; case SMTP_DISK_FULL: return *smtp_code_text[17] ; case SMTP_MBOX_HALT: return *smtp_code_text[18] ; case SMTP_TIMEOUT_PX: return *smtp_code_text[19] ; case SMTP_CONN_XMIT: return *smtp_code_text[20] ; case SMTP_CONN_HOPS: return *smtp_code_text[21] ; case SMTP_TIMEOUT_NS: return *smtp_code_text[22] ; case SMTP_ROUTE: return *smtp_code_text[23] ; case SMTP_MBOXBUSY: return *smtp_code_text[24] ; case SMTP_PROCERROR: return *smtp_code_text[25] ; case SMTP_NOSPACE: return *smtp_code_text[26] ; case SMTP_CLIENT_ERR: return *smtp_code_text[27] ; case SMTP_BADCOMMAND: return *smtp_code_text[28] ; case SMTP_BADSYNTAX: return *smtp_code_text[29] ; case SMTP_NOCOMMAND: return *smtp_code_text[30] ; case SMTP_BAD_CMD_SEQ: return *smtp_code_text[31] ; case SMTP_BAD_PARAM: return *smtp_code_text[32] ; case SMTP_BAD_SNDR_ADDR: return *smtp_code_text[33] ; case SMTP_BAD_RCPT_ADDR: return *smtp_code_text[34] ; case SMTP_BAD_HOST: return *smtp_code_text[35] ; case SMTP_BAD_ADDR_TYPE: return *smtp_code_text[36] ; case SMTP_MSG_SIZE: return *smtp_code_text[37] ; case SMTP_BAD_SERVER: return *smtp_code_text[38] ; case SMTP_BAD_SENDER: return *smtp_code_text[39] ; case SMTP_NACK: return *smtp_code_text[40] ; case SMTP_TRYOTHER: return *smtp_code_text[41] ; case SMTP_MBOXFULL: return *smtp_code_text[42] ; case SMTP_BADNAME: return *smtp_code_text[43] ; case SMTP_FAILED: return *smtp_code_text[44] ; }
return *smtp_code_text[45] ; }
static hzString s_EmailSystem ; // Email unique-Id seed
/* ** MIME Types */
/* ** Generic mail functions */
bool GenerateMailId (hzString& MailId) { // Category: Mailer // // Purpose: Generate a unique mail id for sending out emails. The mail id will be of the form:- // YYYYMMDDHHMMSS.pid.email_system_name@hostname. // The default for email_system_name is 'hadronzoo-mailer'. // // Arguments: 1) MailId Unique Mail Id supplied as a string // // Returns: True If operation successful // False Otherwise
hzChain Z ; // Working chain buffer hzXDate d ; // Current system time and date stamp
if (!s_EmailSystem) s_EmailSystem = "hadronzoo-mailer" ;
d.SysDateTime() ;
Z.Printf("%04d%02d%02d%02d%02d%02d.%d.%s@%s", d.Year(), d.Month(), d.Day(), d.Hour(), d.Min(), d.Sec(), getpid(), *s_EmailSystem, *_hzGlobal_Hostname) ;
MailId = Z ; return MailId ? true : false ; }
void CreateMessageID (hzString& mailId, const hzDomain& domain) { // Category: Mailer // // As described in the Epistula document, the CreateMessageID function generates ids with the following components:- // // - Date and time of the form YYYYMMDD.hhmmss // - A 'last second' counter or LSC (see below) // - The client IP address // - A nmemonc of 'ep' (Epistula) or 'ds' (Dissemino) // // So the overall form is YYYYMMDD.hhmmss.LSC_clientIP_ep@domainName // // To cope with system clock retartations this function uses a static variable of 'latest known time' (LKT) and a dynamic variable of 'last-second' counter (LSC). The LKT has // an intial value of midnight, Jan 1st, 0000. The process begins by reading the clock to an accuracy of one second. If the clock shows a LATER time than the LKT, the LKT is // set equal to the clock and the LSC is set to 0 (this always happens in the first instance). If the clock shows a time EQUAL to or EARLIER than the LKT, the LKT is unchanged // but the LSC is incremented by 1. In ALL cases the id is formed of the LKT and the LSC, however where the clock was found to have an EARLIER time than the LKT, the LKT and // the LSC are separated by an underscore rather than a period. // // Arguments: 1) MailId This will contain the generate message id. // 2) domain Domain name (final part of id) // // Returns: None
static hzXDate LKT ; // Last known time
hzChain Z ; // Working chain buffer hzXDate now ; // Current time uint32_t LSC ; // Last second counter bool bUscore = false ; // Use underscore
// Read system clock now.SysDateTime() ;
if (now.AsEpoch() > LKT.AsEpoch()) { LKT = now ; LSC = 0 ; } else { if (now.AsEpoch() < LKT.AsEpoch()) bUscore = true ; LSC++ ; }
Z.Printf("%04d%02d%02d%02d%02d%02d", LKT.Year(), LKT.Month(), LKT.Day(), LKT.Hour(), LKT.Min(), LKT.Sec()) ; if (bUscore) Z.AddByte(CHAR_USCORE) ; else Z.AddByte(CHAR_PERIOD) ; Z.Printf("%d", LSC) ; Z.AddByte(CHAR_AT) ; Z << domain ;
//Z.Printf("%04d%02d%02d%02d%02d%02d.%d.%s@%s", // d.Year(), d.Month(), d.Day(), d.Hour(), d.Min(), d.Sec(), getpid(), *s_EmailSystem, *_hzGlobal_Hostname) ;
mailId = Z ; }
/* ** hzEmail member functions */
void hzEmail::Clear (void) { // Clear hzEmail instance // // Arguments: None // Returns: None
m_Recipients.Clear() ; // List of main recipient(s) m_CC.Clear() ; // List of cc recipient(s) m_BCC.Clear() ; // List of bcc recipient(s) m_SendAttach.Clear() ; // List of file attachments (added by Attach() as part of forming an outgoing email) m_Attach.Clear() ; // List of attached files m_Inline.Clear() ; // List of inline message parts m_Hdrs.Clear() ; // Message headers: Note this is only filled by Import. Composure of an outgoing message does not populate this list.
m_Final.Clear() ; // Fully composed email m_Text.Clear() ; // Body of email (Text part) m_Html.Clear() ; // Body of email (Html part) m_Err.Clear() ; // For error reporting (e.g. failed import) m_Date.Clear() ; // Date of email (recv only) m_Id.Clear() ; // Mail id (typically as set by first SMTP server to handle it) m_DomainOrig.Clear() ; // Sender's domain (all possible sender address varients must agree on this) m_RealReply.Clear() ; // Real name given in ReplyTo header (if any) m_RealFrom.Clear() ; // Real name of sender m_RealTo.Clear() ; // Real name of recipient (rarely used) m_Subject.Clear() ; // Subject m_AddrReturn.Clear() ; // Return address (Return-path header) m_AddrReply.Clear() ; // Address given in the ReplyTo header m_AddrRelay.Clear() ; // Address of sender as established in SMTP session. Epistula saves this as X-Epistula-Ingress m_AddrFrom.Clear() ; // Address of sender given by From header m_AddrTo.Clear() ; // Address of primary recipient m_SenderIP.Clear() ; // Sender IP address
// Reset types m_ContType = HZ_CONTENT_TYPE_UNDEFINED ; m_Encoding = HZ_CONTENT_ENCODE_UNDEFINED ; }
// FnSet: Mail Composition // Category: Mail Composition // // Func: hzEmail::SetSender(const char* cpSender, const char* cpRealname) // Func: hzEmail::AddRecipient(hzEmaddr& ema) // Func: hzEmail::AddRecipientCC(hzEmaddr& ema) // Func: hzEmail::AddRecipientBCC(hzEmaddr& ema) // Func: hzEmail::SetSubject(const char* cpSubject) // Func: hzEmail::AddBody(const char* msg) // Func: hzEmail::AddBody(hzString& S) // Func: hzEmail::AddBody(hzChain& Z) // Func: hzEmail::AddAttachment(const char* dir, const char* fname, hzMimetype mtype) // Func: hzEmail::Compose(void)
hzEcode hzEmail::SetSender (const char* cpSender, const char* cpRealname) { // Category: Mail Composition // // Set the email's sender. // // Arguments: 1) cpSender The sender email address // 2) cpRealname The apparent from field // // Returns: E_ARGUMENT If the sender email address is not supplied // E_FORMAT If the sender email address is malformed // E_OK If this email sender is successfully set
_hzfunc("hzEmail::SetSender") ;
if (!cpSender || !cpSender[0]) { hzerr(E_ARGUMENT, "No sender supplied") ; return E_ARGUMENT ; }
if (!IsEmaddr(cpSender)) { hzerr(E_FORMAT, "The supplied sender is not a valid email address") ; return E_FORMAT ; }
m_AddrFrom = cpSender ; m_RealFrom = cpRealname ; return E_OK ; }
hzEcode hzEmail::AddRecipient (hzEmaddr& ema) { // Category: Mail Composition // // Add a 'To' recipient to a hzEmail instance using a formal email address (hzEmaddr) as argument // // Arguments: 1) ema Email address to be added to recipient list // // Returns: E_ARGUMENT If the supplied recipient email address is not set // E_OK If the recipient is added
if (!ema) return E_ARGUMENT ;
return m_Recipients.Add(ema) ; }
hzEcode hzEmail::AddRecipientCC (hzEmaddr& ema) { // Category: Mail Composition // // Add a 'Cc' recipient to a hzEmail instance using a formal email address (hzEmaddr) as argument // // Arguments: 1) ema Email address to be added to CC recipient list // // Returns: E_ARGUMENT If the supplied recipient email address is not set // E_OK If the recipient is added
if (!ema) return E_NOINIT ;
return m_CC.Add(ema) ; }
hzEcode hzEmail::AddRecipientBCC (hzEmaddr& ema) { // Category: Mail Composition // // Add a 'Bcc' recipient to a hzEmail instance using a formal email address (hzEmaddr) as argument // // Arguments: 1) ema Email address to be added to BCC recipient list // // Returns: E_ARGUMENT If the supplied recipient email address is not set // E_OK If the recipient is added
if (!ema) return E_NOINIT ;
return m_BCC.Add(ema) ; }
hzEcode hzEmail::AddRecipient (const char* cpRecipient) { // Category: Mail Composition // // Add a 'To' recipient to a hzEmail instance using an informal email address (const char*) as argument // // Arguments: 1) ema Email address to be added to recipient list // // Returns: E_ARGUMENT If the recipient email address is not supplied // E_FORMAT If the recipient email address is malformed // E_OK If the recipient is added
hzEmaddr e ; // Email address to add
if (!cpRecipient) return E_ARGUMENT ;
e = cpRecipient ; if (!e) return E_FORMAT ;
return m_Recipients.Add(e) ; }
hzEcode hzEmail::AddRecipientCC (const char* cpRecipient) { // Category: Mail Composition // // Add a 'Cc' recipient to a hzEmail instance using an informal email address (const char*) as argument // // Arguments: 1) ema Email address to be added to CC recipient list // // Returns: E_ARGUMENT If the recipient email address is not supplied // E_FORMAT If the recipient email address is malformed // E_OK If the recipient is added
hzEmaddr e ; // Email address to add
e = cpRecipient ; if (!e) return E_MEMORY ;
return m_CC.Add(e) ; }
hzEcode hzEmail::AddRecipientBCC (const char* cpRecipient) { // Category: Mail Composition // // Add a 'Bcc' recipient to a hzEmail instance using an informal email address (const char*) as argument // // Arguments: 1) ema Email address to be added to BCC recipient list // // Returns: E_ARGUMENT If the recipient email address is not supplied // E_FORMAT If the recipient email address is malformed // E_OK If the recipient is added
hzEmaddr e ; // Email address to add
e = cpRecipient ; if (!e) return E_MEMORY ;
return m_BCC.Add(e) ; }
hzEcode hzEmail::SetSubject (const char* cpSubject) { // Category: Mail Composition // // Set the subject of the email. // // Arguments: 1) cpSubject Subject matter // // Returns: E_ARGUMENT If the subject matter is not supplied // E_OK If the subject is set
if (!cpSubject || !cpSubject[0]) return E_ARGUMENT ;
m_Subject = cpSubject ; return E_OK ; }
hzEcode hzEmail::AddBody (hzChain& Z) { // Category: Mail Composition // // Addd chain content to the email body. Can be called multiple times. // // Arguments: 1) Z Mail body as chain // // Returns: E_NODATA If the supplied chain is empty // E_OK If the body is added
if (!Z.Size()) return E_NODATA ;
m_Text += Z ; return E_OK ; }
hzEcode hzEmail::AddBody (hzString& S) { // Category: Mail Composition // // Addd a string to the email body. Can be called multiple times. // // Arguments: 1) S Mail body as string // // Returns: E_NODATA If the supplied body is empty // E_OK If the body is added
if (!S) return E_NODATA ;
m_Text += S ; return E_OK ; }
hzEcode hzEmail::AddBody (const char* msg) { // Category: Mail Composition // // Add a char string to the email body. Can be called multiple times. // // Arguments: 1) msg Mail body as null terminated string // // Returns: E_NODATA If the supplied chain is empty // E_OK If the body is added
if (!msg || !msg[0]) return E_NODATA ;
m_Text += msg ; return E_OK ; }
hzEcode hzEmail::AddAttachment (const char* dir, const char* fname, hzMimetype mtype) { // Category: Mail Composition // // Add an attachment using both a directory path and a filename to name the file. // // Arguments: 1) dir Directory // 2) fname Filename // 3) mtype MIME type // // Returns: E_ARGUMENT If either the directory, filename or MIME type is not supplied // E_NOTFOUND If the file suggested as an attachment does not exist // E_OK If the attachment was added //
_hzfunc("hzEmail::AddAttachment(1)") ;
_efile A ; // Attchement to add
if (!dir || !dir[0]) { hzerr(E_ARGUMENT, "No directory supplied") ; return E_ARGUMENT ; }
if (!fname || !fname[0]) { hzerr(E_ARGUMENT, "No filename supplied") ; return E_ARGUMENT ; }
if (mtype == HMTYPE_INVALID) { hzerr(E_ARGUMENT, "Invalid MIME type") ; return E_ARGUMENT ; }
A.m_Filepath = dir ; A.m_Filepath += "/" ; A.m_Filepath += fname ; A.m_eType = mtype ;
if (TestFile(*A.m_Filepath) != E_OK) return E_NOTFOUND ;
return m_SendAttach.Add(A) ; }
hzEcode hzEmail::AddAttachment (const char* fpath, hzMimetype mtype) { // Category: Mail Composition // // Add an attachment using a full file path to name the file. // // Arguments: 1) fpath Full path to file // 2) mtype MIME type // // Returns: E_ARGUMENT If either the filename or MIME type is not supplied // E_NOTFOUND If the file suggested as an attachment does not exist // E_OK If the attachment was added
_hzfunc("hzEmail::AddAttachment(2)") ;
_efile A ; // Attchement to add
if (!fpath || !fpath[0]) return hzerr(E_ARGUMENT, "No filename supplied") ; if (mtype == HMTYPE_INVALID) return hzerr(E_ARGUMENT, "Invalid MIME type") ;
if (TestFile(fpath) != E_OK) return E_NOTFOUND ;
A.m_Filepath = fpath ; A.m_eType = mtype ;
return m_SendAttach.Add(A) ; }
hzEcode hzEmail::AddAttachment (const char* fpath) { // Category: Mail Composition // // Add an attachment using a full file path to name the file, but do not specify the file's MIME type. This will be assigned later on the basis of file ending. // // Arguments: 1) fpath Full path to file // // Returns: E_ARGUMENT If the filename is not supplied // E_NOTFOUND If the file suggested as an attachment does not exist // E_OK If the attachment was added
_hzfunc("hzEmail::AddAttachment(3)") ;
_efile A ; // Attchement to add
if (!fpath || !fpath[0]) return hzerr(E_ARGUMENT, "No filename supplied") ;
if (TestFile(fpath) != E_OK) return E_NOTFOUND ;
A.m_Filepath = fpath ;
return m_SendAttach.Add(A) ; }
hzEcode hzEmail::Compose (void) { // Category: Mail Composition // // Compose is called strictly as part of an email origination process. Once everything has been added in - the sender, the complete list of reciepients, the subject, the body, // and files named as attachments, Compose is called to compile into a single chain, the complete message as it will be relayed. // // Arguments: None // // Returns: E_NOTFOUND If any attachment filepath specified does not exist // E_TYPE If any attachment filepath names a directory entry that is not a file // E_NODATA If any attachment filepath is a file but empty // E_OPENFAIL If any attachment file specified could not be opened for reading // E_OK If this email message was successfully composed
_hzfunc("hzEmail::Compose") ;
hzList<hzEmaddr>::Iter rx ; // Recipients iterator hzList<_efile>::Iter iA ; // Attachments iterator
ifstream is ; // Attachment input stream hzEmaddr ema ; // Email address _efile A ; // Attachment hzXDate now ; // Current time and date stamp hzChain filedata ; // Working output chain hzLogger* plog ; // Current thread logger const char* i ; // Forward slash position uint32_t pid ; // Process id (part of mail-id) char idbuf[60] ; // Mail-Id buffer hzEcode rc = E_OK ; // Return code
plog = GetThreadLogger() ; if (!plog) Fatal("No thrread logger\n") ;
m_Final.Clear() ; m_Final.Printf("From: %s <%s>\r\n", *m_RealFrom, *m_AddrFrom) ;
// if (_hzGlobal_Debug & HZ_DEBUG_MAILER) // plog->Log("Have a total of %d std recipients\n", m_Recipients.Count()) ;
// Deal with main recipients rx = m_Recipients ; ema = rx.Element() ; m_Final.Printf("To: %s\r\n", *ema) ;
for (rx++ ; rx.Valid() ; rx++) { ema = rx.Element() ; m_Final.Printf(" %s\r\n", *ema) ; }
// Deal with carbon copy recipients if (m_CC.Count()) { rx = m_CC ; ema = rx.Element() ; m_Final.Printf("Cc: %s\r\n", *ema) ;
for (rx++ ; rx.Valid() ; rx++) { ema = rx.Element() ; m_Final.Printf(" %s\r\n", *ema) ; } }
// Deal with blind carbon copy recipients if (m_BCC.Count()) { rx = m_BCC ; ema = rx.Element() ; m_Final.Printf("Bcc: %s\r\n", *ema) ;
for (rx++ ; rx.Valid() ; rx++) { ema = rx.Element() ; m_Final.Printf(" %s\r\n", *ema) ; } }
m_Final.Printf("Subject: %s\r\n", *m_Subject) ;
if (m_SendAttach.Count()) { now.SysDateTime() ; pid = getpid() ; sprintf(idbuf, "%d--%04d%02d%02d%02d%02d%02d.%06d--%x", pid, now.Year(), now.Month(), now.Day(), now.Hour(), now.Min(), now.Sec(), now.uSec(), pid) ;
m_Final << "MIME-Version: 1.0\r\n" ; m_Final.Printf("Content-Type: multipart/mixed; boundary=\"%s\"\r\n\r\n", idbuf) ; m_Final << "This is a multi-part message in MIME format.\r\n\r\n" ; m_Final.Printf("--%s\r\n", idbuf) ; m_Final << "Content-Type: text/plain; charset=utf-8; format=flowed\r\n" ; m_Final << "Content-Transfer-Encoding: 7bit\r\n\r\n" ;
m_Final += m_Text ;
m_Final << "\r\n\r\n" ;
for (iA = m_SendAttach ; iA.Valid() ; iA++) { A = iA.Element() ;
rc = OpenInputStrm(is, A.m_Filepath) ; if (rc != E_OK) break ;
filedata.Clear() ; filedata << is ;
m_Final.Printf("--%s\r\n", idbuf) ;
m_Final << "Content-Type: " ; m_Final << Mimetype2Txt(A.m_eType) ;
i = strrchr(*A.m_Filepath, CHAR_FWSLASH) ; if (i) i++ ; else i = A.m_Filepath ;
m_Final.Printf("; name=\"%s\"\r\n", i) ; m_Final << "Content-Transfer-Encoding: base64\r\n" ; m_Final.Printf("Content-Disposition: attachment; filename=\"%s\"\r\n\r\n", i) ;
Base64Encode(m_Final, filedata) ; m_Final << "\r\n" ; is.close() ; is.clear() ; }
m_Final.Printf("--%s--\r\n", idbuf) ; } else { // No attachements. No multipart MIME thing m_Final << "MIME-Version: 1.0\r\n" ; m_Final << "Content-Type: text/plain; charset=utf-8; format=flowed\r\n" ; m_Final << "Content-Transfer-Encoding: 7bit\r\n\r\n" ; m_Final += m_Text ; m_Final << "\r\n\r\n" ; }
return rc ; }
hzEcode hzEmail::SendSmtp (const char* server, const char* uname, const char* passwd) { // Directly relay the email to the target domain SMTP server for immediate transmission, as opposed to queuing the email to the local SMTP server. This is the fastest means of // establishing the validity of the recipient address, widely used by webapps during new member registration to verify the member's email address. The following scenarios are // taken into account:- // // 1) The DNS may be down or temporarily busy so it is not possible to determine if the domain part of the email address actually exists.# // 2) The DNS provides the mail servers for the domain but none can be connected to at the moment. // 3) The DNS is up but cannot find a mail server for the domain // 4) The mail server was connected to but rejected the sender (the web application on your server) // 5) The mail server was connected to but denied the existance of the recipient. // 6) The mail server accepted the email. // // Of the above, scenarios 1 and 2 are inconclusive. Yet the HTTP response to the form submission (containing the user email address), must be sent without delay. The message // should be queued and the user notified of this. The message should make clear the email could not be verified but if it is a live email address, the verification code will // arrive in due course. Any sessions left unfullfilled after a certain period must be purged. // // Senarios 3, 4 and 5 are terminal. The email will not be sent at any point. Only scenario 6 is where the response can say "an email has been sent". It could be of course, // that the address is incorrect. // // Note that bypassing the local SMTP server foregoes any email management the local SMTP server may offer. If you need a permanent and easily searchable record of the email, // the calling applications will have to make arrangements for this. // // Arguments: 1) server The alien domain SMTP server // 2) uname The username for SMTP auth // 3) passwd The password for SMTP auth // // Returns: E_ARGUMENT If no server, username, password or sender is supplied or if no recipients are specified // E_NOACCOUNT If the recipient does not exist // E_HOSTRETRY If the server is too busy
_hzfunc("hzEmail::SendSmtp") ;
hzList<hzEmaddr>::Iter rx ; // Recipients iterator
hzTcpClient C ; // Client connection to destination server hzChain inp ; // Input chain hzChain oup ; // Output chain chIter ci ; // For iterating the composed chain hzLogger* plog ; // Current thread logger char* i ; // Send buffer populator hzString S ; // Temporary string hzEmaddr e ; // Email address uint32_t nRecv ; // Bytes received by Recv() uint32_t nSend ; // Bytes sent by Send() uint32_t nTotal = 0 ; // Total bytes sent (diagnostics only) char sbuf[HZ_MAXPACKET+4] ; // Send buffer char rbuf[HZ_MAXPACKET+4] ; // Recv buffer SMTPCode smtpCode ; // Server SMTP return code hzEcode rc = E_OK ; // Return code
// Check arguments plog = GetThreadLogger() ; if (!plog) Fatal("No thread logger\n") ;
if (!server || !server[0]) { rc = E_ARGUMENT ; plog->Out("No SMTP server supplied\n") ; } if (!uname || !uname[0]) { rc = E_ARGUMENT ; plog->Out("No SMTP username supplied\n") ; } if (!passwd || !passwd[0]) { rc = E_ARGUMENT ; plog->Out("No SMTP password supplied\n") ; }
if (!m_Recipients.Count()) { rc = E_ARGUMENT ; plog->Out("No recipients specified\n") ; } if (!m_AddrFrom) { rc = E_ARGUMENT ; plog->Out("No sender address specified\n") ; }
if (rc != E_OK) return rc ;
if (!m_RealFrom) m_RealFrom = *m_AddrFrom ;
// Connect to port 25 on destination machine. S = server ; rc = C.ConnectStd(S, 25) ; if (rc != E_OK) { hzerr(rc, "Could not conect to SMTP server [%s]", server) ; return rc ; }
// Wait for SMTP greeting C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (nRecv >= 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ;
smtpCode = _getSmtpCode(rbuf) ; if (smtpCode != SMTP_READY) { plog->Out("%d Server not ready so quiting\n", smtpCode) ; rc = E_HOSTRETRY ; goto Quit ; }
// Send the EHLO command sprintf(sbuf, "EHLO %s\r\n", *m_AddrFrom) ;
if ((rc = C.Send(sbuf, strlen(sbuf))) != E_OK) { plog->Out("Could not send HELO command\n") ; goto Quit ; }
if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
// Recv the server responds if ((rc = C.Recv(rbuf, nRecv, HZ_MAXPACKET)) != E_OK) { plog->Out("Could not get ACK to EHLO msg\n") ; goto Quit ; } if (nRecv >= 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ;
smtpCode = _getSmtpCode(rbuf) ; if (smtpCode != SMTP_OK) { rc = E_PROTOCOL ; plog->Out("Expected ACK so quitting\n") ; goto Quit ; } if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s\n", rbuf) ;
// If there is an AUTH, do this first if (uname && passwd) { if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Have username of %s and password of %s so will auth\n", uname, passwd) ;
strcpy(sbuf, "AUTH LOGIN\r\n") ; if ((rc = C.Send(sbuf, strlen(sbuf))) != E_OK) { plog->Out("Could not send MAIL FROM message\n") ; goto Quit ; } if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
// Get back the message 'send username' (written in base64) C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ;
smtpCode = _getSmtpCode(rbuf) ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ; if (smtpCode != SMTP_LOGINFO) { rc = E_BADSENDER ; plog->Out("Expected code 334. Got instead code %d\n", smtpCode) ; goto Quit ; }
// Send the username inp.Clear() ; oup.Clear() ; inp = uname ; Base64Encode(oup, inp) ; oup << "\r\n" ; S = oup ;
if ((rc = C.Send(*S, S.Length())) != E_OK) { plog->Out("Could not send username\n") ; goto Quit ; } if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", *S) ;
// Expect the message 'send password' (written in base64) C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
smtpCode = _getSmtpCode(rbuf) ; if (smtpCode != SMTP_LOGINFO) { plog->Out("Expected code 334 (request for password). Got %d\n", smtpCode) ; rc = E_BADSENDER ; goto Quit ; }
// Send the password inp.Clear() ; oup.Clear() ; inp = passwd ; Base64Encode(oup, inp) ; oup << "\r\n" ; S = oup ;
if ((rc = C.Send(*S, S.Length())) != E_OK) { plog->Out("Could not send password\n") ; goto Quit ; } if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", *S) ;
// Expect the 235 Go Ahead C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
smtpCode = _getSmtpCode(rbuf) ; if (smtpCode != SMTP_GO_AHEAD) { plog->Out("Expected code 235 (Login OK, Go Ahead). Got %d\n", smtpCode) ; rc = E_BADSENDER ; goto Quit ; } }
/* ** Now send the sender details */
sprintf(sbuf, "MAIL FROM: <%s>\r\n", *m_AddrFrom) ; if ((rc = C.Send(sbuf, strlen(sbuf))) != E_OK) { plog->Out("Could not send MAIL FROM message\n") ; goto Quit ; } if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
// Expect 'sender ok' C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
smtpCode = _getSmtpCode(rbuf) ; if (smtpCode != SMTP_OK) { plog->Out("Expected a 250 (OK) code. Got %d\n", smtpCode) ; rc = E_BADSENDER ; goto Quit ; }
/* ** Now send the reciprient details */
for (rx = m_Recipients ; rx.Valid() ; rx++) { e = rx.Element() ;
sprintf(sbuf, "RCPT TO: <%s>\r\n", *e) ; C.Send(sbuf, strlen(sbuf)) ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
// Expect 'recipient ok'
rc = C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (rc != E_OK) { plog->Out("Broken pipe while sending recipient details\n") ; goto Quit ; }
if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
smtpCode = _getSmtpCode(rbuf) ; if (smtpCode != SMTP_OK && smtpCode != SMTP_GO_AHEAD) { plog->Out("Expected a 250 (OK) or 235 (Go Ahead) code. Got %d\n", smtpCode) ; rc = E_NOACCOUNT ; goto Quit ; } }
// Carbon copy recipients for (rx = m_CC ; rx.Valid() ; rx++) { e = rx.Element() ;
sprintf(sbuf, "RCPT TO: <%s>\r\n", *e) ; C.Send(sbuf, strlen(sbuf)) ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
// Expect 'recipient ok'
rc = C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (rc != E_OK) { plog->Out("Broken pipe while sending recipient details\n") ; goto Quit ; }
if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
smtpCode = _getSmtpCode(rbuf) ; if (smtpCode != SMTP_OK) { plog->Out("Target user not ok so quiting\n") ; rc = E_NOACCOUNT ; goto Quit ; } }
// Blind carbon copy recipients for (rx = m_BCC ; rx.Valid() ; rx++) { e = rx.Element() ;
sprintf(sbuf, "RCPT TO: <%s>\r\n", *e) ; C.Send(sbuf, strlen(sbuf)) ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
// Expect 'recipient ok'
rc = C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (rc != E_OK) { plog->Out("Broken pipe while sending recipient details\n") ; goto Quit ; }
if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
smtpCode = _getSmtpCode(rbuf) ; if (smtpCode != SMTP_OK) { plog->Out("Target user not ok so quiting\n") ; rc = E_NOACCOUNT ; goto Quit ; } }
// Now send the data marker
sprintf(sbuf, "DATA\r\n") ; C.Send(sbuf, strlen(sbuf)) ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
// The target should ask for the mail // 354 Enter mail, end with "." on a line by itself
C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
// So we send it
if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Sending %d bytes of data\n", m_Final.Size()) ;
//m_Final.Rewind() ;
// Repeat calls to Send() to transmit email message ci = m_Final ; for (rc = E_OK ; rc == E_OK ;) { for (i = sbuf, nSend = 0 ; !ci.eof() && nSend < HZ_MAXPACKET ; nSend++, ci++) *i++ = *ci ;
if (nSend == 0) break ; nTotal += nSend ; rc = C.Send(sbuf, nSend) ; }
if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Sent %d of %d bytes\n", nTotal, m_Final.Size()) ;
// Send terminator sequence strcpy(sbuf, ".\r\n") ; C.Send(sbuf, strlen(sbuf)) ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
//rc = C.Send(m_Final) ; if (rc != E_OK) { plog->Out("Could not transmit msg body\n") ; goto Quit ; }
// Target should send us a message like // 250 SAA00984 Message accepted for delivery
C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
Quit: // Now send QUIT command
sprintf(sbuf, "QUIT\r\n") ; C.Send(sbuf, strlen(sbuf)) ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Client -> %s", sbuf) ;
// Expect the following msg from the target // 221 osuks.densitron.net closing connection
C.Recv(rbuf, nRecv, HZ_MAXPACKET) ; if (nRecv > 0) rbuf[nRecv] = 0 ; else rbuf[0] = 0 ; if (_hzGlobal_Debug & HZ_DEBUG_MAILER) plog->Out("Server -> %s", rbuf) ;
// It's all over!
C.Close() ; return rc ; }
hzEcode hzEmail::SendEpistula (hzChain& report) { // Directly inject email into the epistula mail que. // // Arguments: 1) report The error report of the send operation
_hzfunc("hzEmail::SendEpistula") ;
static uint32_t nSeq = 1000 ;
hzList<hzEmaddr>::Iter R ; // Recipients iterator
ofstream os ; // Output stream hzChain Z ; // Working output chain hzString S ; // Working string hzEmaddr ema ; // Email address char cvId[12] ; // Mail-id
// Create mail-id sprintf(cvId, "%04x.%04x", getpid(), ++nSeq) ;
// Create body file Z.Printf("/usr/epistula/mailque/%s_body", cvId) ; S = Z ; os.open(*S) ; if (os.fail()) { hzerr(E_WRITEFAIL, "Cannot open mail item file (%s) for writing", *S) ; return E_WRITEFAIL ; }
os << m_Final ; os.close() ; os.clear() ;
// Create header file Z.Clear() ; Z.Printf("/usr/epistula/mailque/%s_head", cvId) ; S = Z ; os.open(*S) ; if (os.fail()) { hzerr(E_WRITEFAIL, "Cannot open mail item file (%s) for writing", *S) ; return E_WRITEFAIL ; }
// Record delivery info os << "from : " << m_AddrFrom << "\r\n" ; if (m_RealFrom.Length()) os << "announce : " << m_RealFrom << "\r\n" ; else os << "announce : " << m_AddrFrom << "\r\n" ; os << "ip_addr : " << "127.0.0.1" << "\r\n" ;
//os << "resolved : " << m_AddrFrom.GetDomain() << "\r\n" ; os << "resolved : " << m_AddrFrom << "\r\n" ; os << "mail_id : " << cvId << "\r\n" ;
// Group recipients by domain for (R = m_Recipients ; R.Valid() ; R++) { ema = R.Element() ; os << "relay_to : " << ema.GetDomain() << "\r\n" ; } os.close() ;
return E_OK ; }
/* ** Import and support functions */
static bool _reademaddr (hzString& aux, hzEmaddr& ema, chIter& ci) { // Support fuction to hzEmail::Import(). This function extracts an email address (and possibly the 'real name'), from a sequence that can be any of the following forms:- // // a) Straight email address, no preceeding name e.g. john.doe@myorg.com // b) Address only but in angle brackets e.g. <john.doe@myorg.com> // c) Address preceeded by name in quotes e.g. "John Doe" <john.doe@myorg.com> // d) Address preceeded by name without quotes e.g. John Doe <john.doe@myorg.com> // // Arguments: 1) aux Aux string (expected to be common name of the address holder) // 2) ema Reference to email address instance // 2) ci The current chain iterator // 3) end End of line // // Returns: True If the email address was established // False Otherwise
_hzfunc(__func__) ;
hzChain W ; // For building value chIter zi ; // Input iterator hzString S1 ; // Temp string hzString S2 ; // Temp string
aux.Clear() ; ema.Clear() ;
// Clear leading whitespace for (zi = ci ; *zi == CHAR_TAB || *zi == CHAR_SPACE ; zi++) ; if (zi.eof()) return false ;
if (*zi != CHAR_LESS) { // Could be case (a), (c) or (d) if (*zi == CHAR_DQUOTE) { // Case (c) for (zi++ ; *zi >= CHAR_SPACE && *zi != CHAR_DQUOTE ; zi++) W.AddByte(*zi) ; if (*zi == CHAR_DQUOTE) S1 = W ; W.Clear() ; } else { // Case (a) if string encountered is an email address, (d) otherwise for (; *zi >= CHAR_SPACE ; zi++) { if (*zi == CHAR_SPACE && zi == " <") break ; W.AddByte(*zi) ; } S1 = W ; W.Clear() ; } for (zi++ ; *zi == CHAR_TAB || *zi == CHAR_SPACE ; zi++) ; }
if (*zi == CHAR_LESS) { for (zi++ ; *zi > CHAR_SPACE && *zi != CHAR_MORE ; zi++) W.AddByte(*zi) ; if (*zi == CHAR_MORE) S2 = W ; W.Clear() ; }
if (S1 && !S2) { if (IsEmaddr(S1)) ema = S1 ; } else if (S2 && !S1) { if (IsEmaddr(S2)) ema = S2 ; } else if (S1 && S2) { //S1.TopAndTail() ; aux = S1 ; if (IsEmaddr(S2)) ema = S2 ; } else return false ;
if (!ema) return false ; return true ; }
bool _readangle (hzString& str, hzChain::Iter& ci) { // Support function for hzEmail::Import(). The chain iterator is expected to be at the start of a <> block. This function places the content of the <> block into the supplied // string. // // Arguments: 1) str String to be populated // 2) ci The current chain iterator // 3) end End of line // // Returns: True If the email address was established // False Otherwise
_hzfunc("_readangle") ;
chIter zi ; // Input chain iterator hzChain W ; // For building value
if (ci.eof()) return false ; zi = ci ; if (*zi != CHAR_LESS) return false ;
for (zi++ ; !zi.eof() && *zi > CHAR_SPACE && *zi != CHAR_MORE ; zi++) W.AddByte(*zi) ;
if (*zi != CHAR_MORE) return false ;
str = W ; W.Clear() ; ci = zi ; return true ; }
hzEcode _readStrval (hzString& S, hzChain::Iter& ci) { // Support function for hzEmail::Import(). Get a string regardless of weather it is quoted or not // // Arguments: 1) S The string to be populated // 2) ci The chain iterator // 3) end End of line // // Returns: E_FORMAT If there is a single/double quote mismatch // E_OK If a string was garnered.
_hzfunc(__func__) ;
hzChain word ; // Word being garnered chIter zi ; // Chain iterator int32_t quote ; // Quote char (either double or single quote)
S.Clear() ;
zi = ci ; quote = *zi == CHAR_DQUOTE ? CHAR_DQUOTE : *zi == CHAR_SQUOTE ? CHAR_SQUOTE : 0 ;
if (quote) { for (zi++ ; !zi.eof() && *zi != quote && *zi >= CHAR_SPACE ; zi++) word.AddByte(*zi) ;
if (*zi != quote) return E_FORMAT ; zi++ ; } else { for (; !zi.eof() && *zi >= CHAR_SPACE && *zi != CHAR_SCOLON ; zi++) { if (*zi == CHAR_SPACE && zi == " <") { zi++ ; break ; } word.AddByte(*zi) ; } }
//if (*zi == CHAR_SCOLON) //zi++ ;
S = word ; ci = zi ; return E_OK ; }
/* ** Email Import */
static void _find_hdr_end (chIter& end, const chIter& zi) { // Support function for hzEmail::Import(), _part_decode() and _part_process. Finds the end of a message or part header. This can either be used to skip the header, or to limit // iteration when processing the header. // // It is assummed herein that the supplied iterator is at the start of the line. This function iterates to EOL, then checks if there is a SPACE or TAB after the CR and NL. If // there is, the next line is iterated to EOL and the check is repeated. This continues until the CR and NL is followed by either another CR and NL or a non-whitespace char. // // Note that the end is set at the CR of the first CR NL pair that is not followed by a SPACE or TAB // // Arguments: 1) eH End of header // 2) zi Current iterator // // Returns: None
_hzfunc(__func__) ;
chIter xi ; // Working iterator
for (end = zi ; !end.eof() ; end++) { if (*end == CHAR_CR) { if (end == "\r\n ") { end += 2 ; continue ; } if (end == "\r\n\t") { end += 2 ; continue ; } break ; } } }
static hzEcode _part_decode (hzChain& Final, const hzChain& Z, hzContentEncoding eCE) { // Support function for hzEmail::Import(). Decode content of message part
_hzfunc(__func__) ;
hzEcode rc = E_OK ; // Return code
Final.Clear() ;
switch (eCE) { case HZ_CONTENT_ENCODE_UNDEFINED: // Content encoding not defined case HZ_CONTENT_ENCODE_7BIT: // Content is not encoded but chars are limited to lower ASCII case HZ_CONTENT_ENCODE_8BIT: // Content is not encoded and chars may be upper ASCII case HZ_CONTENT_ENCODE_BINARY: // Content is not encoded and chars may be anything
Final = Z ; break ;
case HZ_CONTENT_ENCODE_BASE64:
rc = Base64Decode(Final, Z) ; if (rc != E_OK) Final = Z ; break ;
case HZ_CONTENT_ENCODE_QUOTE_PRINT:
rc = QPDecode(Final, Z) ; if (rc != E_OK) Final = Z ; break ; }
return rc ; }
hzEcode hzEmail::_part_process (chIter& zi, const hzString& mark, uint32_t nLevel) { // Process message parts until termination sequence (--boundary--) // // Message parts take the following form:- // a) A line of the form --boundary // b) Headers to state 'filename', content type, content encoding and size // c) A blank line // d) The content // e) Another blank line // f) A terminating line of the form --boundary-- // // The headers may state another boundary, in which case one or more message parts will be enclosed between (a) and (f). These parts will be of the same form (a - f), and are // handled by a recursive call to this function. // // Arguments: 1) zi Input chain iterator // 2) mark Applicable boundary // 3) nLevel Recursion level (multipart directive) // // Returns: E_FORMAT Format error
_hzfunc("hzEmail::_part_process") ;
hzEmpart epart ; // Email part hzChain tmpCh ; // Result of decoding chIter eH ; // End of header line chIter sol ; // Marks start of line hzString mark2 ; // Boundary introduced by multipart/alternative hzString markStart ; // Boundary value prepended with '--' hzString markEnd ; // Boundary value prepended with '--' and postpended with '--' hzEcode rc = E_OK ; // Return code
threadLog("Processing at level %u with %s\n", nLevel, *mark) ; markStart = "--" + mark ; markEnd = markStart + "--" ;
// Now at boundary. Process each boundary block as a part if (zi != markStart) { threadLog("Iter not at mark start\n") ; zi.Skipwhite() ; // for (; !zi.eof() ; zi++) // { // if (zi == markStart) // break ; // } }
if (zi != markStart) { threadLog("Iter still not at mark start\n") ; return E_FORMAT ; }
// Process part headers for (; zi == markStart ;) { if (zi == markEnd) { zi += markEnd.Length() ; break ; }
// Move past the start marker epart.Clear() ; zi += markStart.Length() ; zi.Skipwhite() ;
// Process part headers. This may result in processing an inner part threadLog("Processing part at level %u with %s\n", nLevel, *mark) ; for (; !zi.eof() ;) { if (*zi == CHAR_CR) { if (zi == "\r\n\r\n") { // At end of part header break ; } zi++ ; if (*zi == CHAR_NL) { zi++ ; continue ; } }
// Should be at beginning of a header line _find_hdr_end(eH, zi) ; if (zi == "Content-Disposition:") { zi += 21 ; if (zi == "attachment;") { zi += 12 ; if (zi == "filename=") { zi += 9 ; _readStrval(epart.m_Filename, zi) ; threadLog("Set filename to %s\n", *epart.m_Filename) ; } } zi = eH ; continue ; }
if (zi == "Content-Type:") { zi += 14 ;
if (zi == "multipart/") { threadLog("Proc multipart\n") ; // Get boundary and use this to recurse for (; !zi.eof() && *zi != CHAR_CR && zi != CHAR_NL ; zi++) ; if (zi == "\r\n") zi += 2 ;
if (zi == "\tboundary=") { threadLog("Proc boundary\n") ; zi += 10 ; _readStrval(mark2, zi) ; zi = eH ; if (zi == "\r\n") zi += 2 ; // Recurse rc = _part_process(zi, mark2, nLevel+1) ; continue ; } } else { _readStrval(epart.m_ContType, zi) ; zi = eH ; continue ; } }
if (zi == "Content-Transfer-Encoding:") { zi += 27 ; if (zi.Equiv("7bit")) { zi += 4 ; epart.m_Encoding = HZ_CONTENT_ENCODE_7BIT ; } else if (zi.Equiv("8bit")) { zi += 4 ; epart.m_Encoding = HZ_CONTENT_ENCODE_8BIT ; } else if (zi.Equiv("binary")) { zi += 6 ; epart.m_Encoding = HZ_CONTENT_ENCODE_8BIT ; } else if (zi.Equiv("base64")) { zi += 6 ; epart.m_Encoding = HZ_CONTENT_ENCODE_BASE64 ; } else if (zi.Equiv("quoted-printable")) { zi += 16 ; epart.m_Encoding = HZ_CONTENT_ENCODE_QUOTE_PRINT ; } else { threadLog("Unknown Content-Transfer-Encoding value\n") ; return E_FORMAT ; } zi = eH ; continue ; }
// Ignore other headers threadLog("Line %u: Unknown PART Header directive\n", zi.Line()) ; zi = eH ; }
// Now copy everything until the next boundary marker (can be begining or end) for (; !zi.eof() ;) { if (zi == "\r\n") { sol = zi ; sol += 2 ; }
if (*zi == '-') { if (zi == markStart) break ; } epart.m_Content.AddByte(*zi) ; zi++ ; }
if (zi != sol) { threadLog("boundary not at start of line\n") ; return E_FORMAT ; }
// Insert the part if (epart.m_ContType.Contains("text/plain")) { _part_decode(tmpCh, epart.m_Content, epart.m_Encoding) ; threadLog("epart is TEXT (%u bytes)\n", tmpCh.Size()) ; m_Text << tmpCh ; tmpCh.Clear() ; } else if (epart.m_ContType.Contains("text/html")) { _part_decode(tmpCh, epart.m_Content, epart.m_Encoding) ; threadLog("epart is HTML (%u bytes)\n", tmpCh.Size()) ; m_Html << tmpCh ; tmpCh.Clear() ; } else { _part_decode(tmpCh, epart.m_Content, epart.m_Encoding) ; epart.m_Content = tmpCh ; tmpCh.Clear() ;
if (epart.m_Filename) { threadLog("Adding attachment of %d bytes\n", epart.m_Content.Size()) ; m_Attach.Add(epart) ; } else { threadLog("Adding inline of %d bytes\n", epart.m_Content.Size()) ; m_Inline.Add(epart) ; } } }
return rc ; }
hzEcode hzEmail::Import (const hzChain& emRaw, bool bHead) { // Import a serialized email message in IMF (Internet Message Format). // // This hzEmail instance is first cleared, then populated with the supplied IMF datum. To be valid, a message must have a sender, at least one recipient, a date and time and a // message id. For the purpose of HadronZoo::Epistula, a message must have a body. // // Within the message header, there is usually a Content-Type header. This can specify a format such as "text/plain", but this is only where the message body is not comprised // of multiple parts. Email messages are mostly multipart, with a Content-Type of either:- // // multipart/mixed This is for sending files with different Content-Type header fields, either to be displayed upon opening by the mail client, or as attachments. // // multipart/alternative This indicates that each part is an alternative version of the same (or similar) content, e.g. a text part and a HTML part. Each part will have // its own Content-Type header to state the format. // // multipart/related This is used to indicate that each message part is a component of an aggregate whole. It is for messages consisting of a number of inter-related // parts. The message consists of a root part which reference other parts, which may in turn reference other parts. One use would be to send a web // page complete with images in a single message. // // The parts are separated by a boundary sequence which is specified in the line after a multipart Content-Type header. In theory a single boundary sequence would suffice, but // email messages often use one part to specify another boundary sequence. This latter sequence is then used to separate parts, until it is terminated (postpended with --), at // which point use of the previous boundary sequence resumes. Because of this approach, this function calls a recursive support function, _part_process(), to process parts. // // Arguments: emRaw The serialized email message supplied as a hzChain // bHead Limit the import to headers only // // Returns: E_SYNTAX If any aspect of the previously exported email are malformed // E_BADVALUE If essential headers are missing or invalid // E_NODATA If there is no message body // E_OK If the import was successful
_hzfunc("hzEmail::Import") ;
hzChain Part ; // For building header line chIter zi ; // For iteration chIter xi ; // For iteration chIter endHdr ; // End of header (eol) chIter bodyStart ; // Start of body hzEmpart epart ; // Email part hzEmaddr emtmp ; // For holding email addresses in Cc and Bcc hzString filepath ; // Full path to email file hzString mark ; // Currently applicable boundary hzString markStart ; // Boundary value prepended with '--' hzString markEnd ; // Boundary value prepended with '--' and postpended with '--' hzString strval ; // Temp string hzString episId ; // Default Epistula ID (if no message id supplied) uint32_t nP ; // Part counter uint32_t nLen ; // No of chars to advance iterator by hzEcode rc = E_OK ; // Return code
Clear() ;
// go past X-Epistula headers and grab next two lines. Use these lines as a pattern, that we want the last occurence of in the chain. Once the position of this is established, // that is the real content!
m_Err.Clear() ;
// Ensure standard message headers are loaded // if (!_hzGlobal_EmsgHdrs.Count()) // HadronZooInitMessageHdrs() ;
// Pre-process headers in message to hand. //zi = m_Final ; zi = emRaw ; for (; !zi.eof() ;) { if (*zi == CHAR_CR) { // At end of message header block? if (zi == "\r\n\r\n") { zi += 4 ; break ; }
if (zi == "\r\n") zi += 2 ; }
// Discover end of line. Note that \r\n followed by a space or tab, is taken to be a continuation of the line. _find_hdr_end(endHdr, zi) ;
// Now process line switch (*zi) { case CHAR_LC_B: case CHAR_UC_B: if (zi.Equiv("Bcc: ")) { zi += 5 ; if (!_reademaddr(strval, emtmp, zi)) rc = E_SYNTAX ; m_BCC.Add(emtmp) ; threadLog("Bcc: Added %s\n", *emtmp) ; } break ;
case CHAR_LC_C: case CHAR_UC_C: if (zi.Equiv("Cc: ")) { zi += 4 ; if (!_reademaddr(strval, emtmp, zi)) rc = E_SYNTAX ; m_CC.Add(emtmp) ; threadLog("Cc: Added %s\n", *emtmp) ; break ; }
if (zi.Equiv("Content-Type: ")) { zi += 14 ; //i += 14 ; if (zi.Equiv("text/plain;")) { zi += 11 ; m_ContType = HZ_CONTENT_TYPE_TEXT_PLAIN ; } else if (zi.Equiv("text/html;")) { zi += 10 ; m_ContType = HZ_CONTENT_TYPE_TEXT_HTML ; } else if (zi.Equiv("multipart/alternative;")) { zi += 22 ; m_ContType = HZ_CONTENT_TYPE_MULTI_ALTERNATIVE ; } else if (zi.Equiv("multipart/mixed;")) { zi += 16 ; m_ContType = HZ_CONTENT_TYPE_MULTI_MIXED ; } else if (zi.Equiv("multipart/related;")) { zi += 18 ; m_ContType = HZ_CONTENT_TYPE_MULTI_RELATED ; } else { rc = E_SYNTAX ; threadLog("Unknown Content-Type\n") ; } //{ rc = E_SYNTAX ; m_Err << "Unknown Content-Type\n" ; break ; }
if (m_ContType == HZ_CONTENT_TYPE_MULTI_MIXED || m_ContType == HZ_CONTENT_TYPE_MULTI_ALTERNATIVE || m_ContType == HZ_CONTENT_TYPE_MULTI_RELATED) { if (mark) { rc = E_FORMAT ; threadLog("MULTIPART MIX/ALT/REL: Boundary already specified\n") ; } else { for (; *zi ; zi++) { if (*zi == CHAR_LC_B) { //if (!memcmp(i, "boundary=", 9)) if (zi == "boundary=") { zi += 9 ; _readStrval(mark, zi) ; break ; } } }
if (!mark) { rc = E_SYNTAX ; threadLog("MULTIPART MIX/ALT/REL: Expected a boundary to be specified\n") ; } } } break ; }
if (zi == "Content-Transfer-Encoding: ") { zi += 27 ; if (zi == "7bit") m_Encoding = HZ_CONTENT_ENCODE_7BIT ; else if (zi == "8bit") m_Encoding = HZ_CONTENT_ENCODE_8BIT ; else if (zi == "binary") m_Encoding = HZ_CONTENT_ENCODE_8BIT ; else if (zi == "base64") m_Encoding = HZ_CONTENT_ENCODE_BASE64 ; else if (zi == "quoted-printable") m_Encoding = HZ_CONTENT_ENCODE_QUOTE_PRINT ; else { rc = E_SYNTAX ; threadLog("Unknown Content-Transfer-Encoding value\n") ; } } break ;
case CHAR_LC_D: case CHAR_UC_D: if (zi.Equiv("Date: ")) { zi += 6 ; nLen = IsFormalDate(m_Date, zi) ; if (!nLen) threadLog("Date arg must amount to legal date\n") ; } break ;
case CHAR_UC_F: if (zi == "From: ") { zi += 6 ; if (!_reademaddr(m_RealFrom, m_AddrFrom, zi)) { rc = E_SYNTAX ; m_Err << "From: arg must amount to an email address\n" ; } } break ;
case CHAR_UC_M: if (zi == "Message-ID: ") { zi += 12 ; _readangle(m_Id, zi) ; } break ;
case CHAR_LC_R: case CHAR_UC_R: if (zi.Equiv("Return-Path:")) { zi += 12 ; if (!_reademaddr(strval, m_AddrReturn, zi)) { rc = E_SYNTAX ; threadLog("Return-Path must amount to an email address\n") ; } break ; } if (zi.Equiv("Reply-To:")) { zi += 9 ; if (!_reademaddr(m_RealReply, m_AddrReply, zi)) { rc = E_SYNTAX ; threadLog("Reply-To: arg must amount to an email address\n") ; } } break ;
case CHAR_LC_S: case CHAR_UC_S: if (zi.Equiv("Subject: ")) { zi += 9 ; _readStrval(strval, zi) ; m_Subject = strval ; CharsetStringDecode(m_Subject, strval) ; } break ;
case CHAR_LC_T: case CHAR_UC_T: if (zi.Equiv("To: ")) { zi += 4 ; if (!_reademaddr(m_RealTo, emtmp, zi)) { rc = E_SYNTAX ; threadLog("To: arg must amount to an email address\n") ; } if (!m_AddrTo) m_AddrTo = emtmp ; m_Recipients.Add(emtmp) ; } break ;
case CHAR_LC_X: case CHAR_UC_X: if (zi.Equiv("X-Epistula-ServerID: ")) { zi += 21 ; _readangle(episId, zi) ; break ; } if (zi.Equiv("X-Epistula-Ingress: ")) { zi += 20 ; if (!_reademaddr(strval, m_AddrRelay, zi)) { rc = E_SYNTAX ; threadLog("X-Epistula-Ingress: arg must amount to an email address\n") ; } } break ; }
// if (zi != endHdr) // threadLog("Iter not at header end\n") ; zi = endHdr ; }
if (rc != E_OK) { threadLog("IMPORT FAILED\n") ; threadLog(m_Err) ; threadLog("--end--\n") ; return rc ; }
// Check if no date if (!m_Date) { rc = E_BADVALUE ; threadLog("WARNING: No date header\n") ; }
// Check for critical headers if (!m_AddrFrom) { rc = E_BADVALUE ; threadLog("No From address\n") ; }
//if (m_Recipients.Count() == 0 && m_CC.Count() == 0 && m_BCC.Count() == 0) if (!m_AddrTo) { rc = E_BADVALUE ; threadLog("No recipients\n") ; }
// Warn if no mail id if (!m_Id) m_Id = episId ; if (!m_Id) { rc = E_BADVALUE ; threadLog("No mail id\n") ; }
if (rc != E_OK) { threadLog("IMPORT FAILED\n") ; //threadLog(m_Err) ; //threadLog("--end--\n") ; return rc ; }
if (bHead) return rc ;
/* ** Process the body. This may or may not come in blocks marked out by the boundary. */
//zi = bodyStart ; //zi.Skipwhite() ;
if (!mark) { // The email is comprised of a single part with no boundary. Unless this is stated as HTML it is presumed to be TEXT. Part.Clear() ; for (; !zi.eof() ; zi++) { if (*zi == CHAR_CR) { if (zi == "\r\n.\r\n") break ; } Part.AddByte(*zi) ; }
if (m_ContType == HZ_CONTENT_TYPE_TEXT_HTML) _part_decode(m_Html, Part, m_Encoding) ; else _part_decode(m_Text, Part, m_Encoding) ;
//if (rc == E_OK) // m_Err.Clear() ; threadLog("Processed a non-part message\n") ; return rc ; }
// The email is comprised of multiple parts. We skip whitespace and then should be at the start of a boundary
markStart = "--" + mark ; markEnd = markStart + "--" ;
threadLog("Operating with BOUNDARY=%s\n", *mark) ;
if (zi != markStart) { // Assign data to the text or html part until a boudary is encountered threadLog("Unexpected data\n") ; zi.Skipwhite() ; }
for (nP = 0 ; rc == E_OK && zi == markStart ; nP++) { if (zi == markEnd) break ; threadLog("Processing part %u\n", nP) ; rc = _part_process(zi, mark, 1) ; }
//threadLog(m_Err) ; return rc ; }