Skip to content

Commit 2e0de92

Browse files
authored
Merge pull request #30 from jeremydumais/version_1_1_8
Version 1.1.8
2 parents 2a9b14a + f8d5bde commit 2e0de92

15 files changed

+335
-42
lines changed

.github/workflows/cmake.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919

2020

2121
steps:
22-
- uses: actions/checkout@v3
22+
- uses: actions/checkout@v4
2323

2424
# Install OS specific dependencies
2525
- name: Install Windows dependencies

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22

33
All notable changes to this project will be documented in this file
44

5+
## [1.1.8]
6+
7+
## Enhancement / Bug fixes
8+
- Some SMTP server send their list of supported extensions in multiple
9+
buffers like Zoho Mail. The EHLO command when in uncrypted mode, now supports
10+
receiving multiple buffers. In return, a delay of one second must be added for
11+
each segment sent by the SMTP server. For SMTP servers that send the list of
12+
supported extensions in a single segment like Gmail and Live, no additional
13+
delay is added for the EHLO command. This doesn't affect the other commands.
14+
- Now when we send an email to multiple recipients (to or cc), the recipients
15+
appears as a single mail header instead of multiple headers. The old method was
16+
not RFC 5322 compliant.
17+
18+
Before:
19+
20+
21+
22+
23+
24+
After:
25+
26+
27+
28+
529
## [1.1.7]
630

731
### Added

CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ set(PROJECT_SOURCE_FILES ${SRC_PATH}/attachment.cpp
9797
${SRC_PATH}/securesmtpclientbase.cpp
9898
${SRC_PATH}/opportunisticsecuresmtpclient.cpp
9999
${SRC_PATH}/forcedsecuresmtpclient.cpp
100+
${SRC_PATH}/serveroptionsanalyzer.cpp
100101
${SRC_PATH}/stringutils.cpp
101102
${SRC_PATH}/errorresolver.cpp
102103
${SRC_PATH}/cpp/attachment.cpp
@@ -148,7 +149,8 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
148149
add_library(${PROJECT_NAME} SHARED
149150
${PROJECT_SOURCE_FILES} ${VersionFilesOutputVariable})
150151
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
151-
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX)
152+
add_compile_options(/EHsc)
153+
target_compile_options(${PROJECT_NAME} PRIVATE /W3 /WX)
152154
target_compile_definitions(${PROJECT_NAME}
153155
PRIVATE SMTPCLIENT_EXPORTS
154156
INTERFACE NOMINMAX # avoid Win macro definition of min/max, use std one
@@ -250,6 +252,7 @@ if (BUILD_TESTING)
250252
${TEST_SRC_PATH}/opportunisticsecuresmtpclient_unittest.cpp
251253
${TEST_SRC_PATH}/smtpclientbase_unittest.cpp
252254
${TEST_SRC_PATH}/smtpclient_unittest.cpp
255+
${TEST_SRC_PATH}/serveroptionsanalyzer_unittest.cpp
253256
${TEST_SRC_PATH}/errorresolver_unittest.cpp)
254257

255258
target_link_libraries(${PROJECT_UNITTEST_NAME} ${PROJECT_NAME} gtest gtest_main ${PTHREAD})

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ for previous versions.
8585

8686
## 📰 What's new
8787

88+
- Version 1.1.8:
89+
- Some SMTP server send their list of supported extensions in multiple
90+
buffers like Zoho Mail. The EHLO command when in uncrypted mode, now supports
91+
receiving multiple buffers. In return, a delay of one second must be added for
92+
each segment sent by the SMTP server. For SMTP servers that send the list of
93+
supported extensions in a single segment like Gmail and Live, no additional
94+
delay is added for the EHLO command. This doesn't affect the other commands.
95+
- Now when we send an email to multiple recipients (to or cc), the recipients
96+
appears as a single mail header instead of multiple headers. The old method was
97+
not RFC 5322 compliant.
8898
- Version 1.1.7:
8999
- Added support for the XOAUTH2 authentication method.
90100
This change has been made by rcosnita (https://github.com/rcosnita).

src/forcedsecuresmtpclient.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ int ForcedSecureSMTPClient::checkServerGreetings() {
8383
unsigned int waitTime = 0;
8484
ssize_t bytes_received = 0;
8585
while ((bytes_received = BIO_read(getBIO(), outbuf, SERVERRESPONSE_BUFFER_LENGTH)) <= 0 && waitTime < getCommandTimeout()) {
86-
sleep(1);
86+
crossPlatformSleep(1);
8787
waitTime += 1;
8888
}
8989
if (waitTime < getCommandTimeout()) {

src/securesmtpclientbase.cpp

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <openssl/x509_vfy.h>
44
#include <string>
55
#include <utility>
6+
#include "serveroptionsanalyzer.h"
67
#include "smtpclienterrors.h"
78
#include "socketerrors.h"
89
#include "sslerrors.h"
@@ -268,8 +269,20 @@ int SecureSMTPClientBase::getServerSecureIdentification() {
268269
if (tls_command_return_code != EHLO_SUCCESS_CODE) {
269270
return tls_command_return_code;
270271
}
272+
std::string returnedOptions = getLastServerResponse();
273+
// Check that the last returned option has no hyphen otherwise options are
274+
// still missing from the server.
275+
while (!ServerOptionsAnalyzer::containsAllOptions(returnedOptions)) {
276+
int replyCode = getServerReply();
277+
if (replyCode == -1) {
278+
return SSL_CLIENT_INITSECURECLIENT_TIMEOUT;
279+
} else if (replyCode != EHLO_SUCCESS_CODE) {
280+
return replyCode;
281+
}
282+
returnedOptions += "\n" + std::string(getLastServerResponse());
283+
}
271284
// Inspect the returned values for authentication options
272-
setAuthenticationOptions(SMTPClientBase::extractAuthenticationOptions(getLastServerResponse()));
285+
setAuthenticationOptions(SMTPClientBase::extractAuthenticationOptions(returnedOptions.c_str()));
273286
return EHLO_SUCCESS_CODE;
274287
}
275288

@@ -283,10 +296,6 @@ int SecureSMTPClientBase::sendCommand(const char *pCommand, int pErrorCode) {
283296
}
284297

285298
int SecureSMTPClientBase::sendCommandWithFeedback(const char *pCommand, int pErrorCode, int pTimeoutCode) {
286-
unsigned int waitTime {0};
287-
int bytes_received {0};
288-
char outbuf[SERVERRESPONSE_BUFFER_LENGTH];
289-
290299
// Check if we are in the TLS mode of before it
291300
if (mBIO == nullptr) {
292301
return sendRawCommand(pCommand, pErrorCode, pTimeoutCode);
@@ -297,8 +306,21 @@ int SecureSMTPClientBase::sendCommandWithFeedback(const char *pCommand, int pErr
297306
return pErrorCode;
298307
}
299308

309+
int serverReplyCode = getServerReply();
310+
if (serverReplyCode != -1) {
311+
return serverReplyCode;
312+
}
313+
314+
cleanup();
315+
return pTimeoutCode;
316+
}
317+
318+
int SecureSMTPClientBase::getServerReply() {
319+
unsigned int waitTime {0};
320+
int bytes_received {0};
321+
char outbuf[SERVERRESPONSE_BUFFER_LENGTH];
300322
while ((bytes_received = BIO_read(mBIO, outbuf, SERVERRESPONSE_BUFFER_LENGTH)) <= 0 && waitTime < getCommandTimeout()) {
301-
sleep(1);
323+
crossPlatformSleep(1);
302324
waitTime += 1;
303325
}
304326
if (waitTime < getCommandTimeout()) {
@@ -307,7 +329,5 @@ int SecureSMTPClientBase::sendCommandWithFeedback(const char *pCommand, int pErr
307329
addCommunicationLogItem(outbuf, "s");
308330
return extractReturnCode(outbuf);
309331
}
310-
311-
cleanup();
312-
return pTimeoutCode;
332+
return -1;
313333
}

src/securesmtpclientbase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class SECURESMTPCLIENTBASE_API SecureSMTPClientBase : public SMTPClientBase {
6565
// Methods to send commands to the server
6666
int sendCommand(const char *pCommand, int pErrorCode) override;
6767
int sendCommandWithFeedback(const char *pCommand, int pErrorCode, int pTimeoutCode) override;
68+
int getServerReply() override;
6869

6970
private:
7071
// Attributes used to communicate with the server

src/serveroptionsanalyzer.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "serveroptionsanalyzer.h"
2+
3+
using namespace jed_utils;
4+
5+
// Trim from start
6+
bool ServerOptionsAnalyzer::containsAllOptions(const std::string &optionsStr) {
7+
std::string sanitizedOptionsStr = [&optionsStr]() {
8+
if (optionsStr.size() >= 2 && optionsStr.substr(optionsStr.size() - 2) == "\r\n") {
9+
return optionsStr.substr(0, optionsStr.size() - 2);
10+
} else {
11+
return optionsStr;
12+
}
13+
}();
14+
size_t lastLineStart = sanitizedOptionsStr.rfind("\r\n");
15+
if (lastLineStart == std::string::npos) {
16+
lastLineStart = 0;
17+
} else {
18+
lastLineStart += 2; // Move past the \r\n
19+
}
20+
if (sanitizedOptionsStr.substr(lastLineStart, 4) == "250 ") {
21+
return true;
22+
}
23+
return false;
24+
}

src/serveroptionsanalyzer.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#ifndef SERVEROPTIONSANALYZER_H
2+
#define SERVEROPTIONSANALYZER_H
3+
4+
#include <string>
5+
6+
#ifdef _WIN32
7+
#ifdef SMTPCLIENT_EXPORTS
8+
#define SERVEROPTIONSANALYZER_API __declspec(dllexport)
9+
#else
10+
#define SERVEROPTIONSANALYZER_API __declspec(dllimport)
11+
#endif
12+
#else
13+
#define SERVEROPTIONSANALYZER_API
14+
#endif
15+
16+
namespace jed_utils {
17+
/** @brief The ServerOptionsAnalyzer class provides utility fonctions to
18+
* analyzer the SMTP options that are available by the server.
19+
*/
20+
class SERVEROPTIONSANALYZER_API ServerOptionsAnalyzer {
21+
public:
22+
/**
23+
* @brief Indicate if the server has returned all it's available options
24+
* or of there still a reply with other options to come.
25+
* @param optionsStr The string that contains the option list returned by
26+
* the SMTP server.
27+
*/
28+
static bool containsAllOptions(const std::string &optionsStr);
29+
};
30+
} // namespace jed_utils
31+
32+
33+
#endif // SERVEROPTIONSANALYZER_H

src/smtpclient.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,7 @@ int SmtpClient::sendCommand(const char *pCommand, int pErrorCode) {
104104
int SmtpClient::sendCommandWithFeedback(const char *pCommand, int pErrorCode, int pTimeoutCode) {
105105
return sendRawCommand(pCommand, pErrorCode, pTimeoutCode);
106106
}
107+
108+
int SmtpClient::getServerReply() {
109+
return getRawCommandReply();
110+
}

0 commit comments

Comments
 (0)