18#include "proto_fuzzer/allocation_fault_injection.h"
22namespace proto_fuzzer {
29 : recipient_count_(0),
30 rcpt_command_count_(0),
31 auth_exchange_active_(false),
33 tls_upgrade_started_(false) {}
45const std::string& SendBackpressureCommand() {
46 static const std::string command =
"NOOP " + std::string(16 * 1024,
'S');
56 if (easy ==
nullptr) {
60 case curl::fuzzer::proto::SMTP_PROBE_STARTTLS_TRY:
61 (void)curl_easy_setopt(easy, CURLOPT_USE_SSL,
static_cast<long>(CURLUSESSL_TRY));
63 case curl::fuzzer::proto::SMTP_PROBE_STARTTLS_REQUIRE:
64 case curl::fuzzer::proto::SMTP_PROBE_STARTTLS_FILTER_ALLOCATION_FAILURE:
65 (void)curl_easy_setopt(easy, CURLOPT_USE_SSL,
static_cast<long>(CURLUSESSL_ALL));
67 case curl::fuzzer::proto::SMTP_PROBE_SEND_BACKPRESSURE:
68 (void)curl_easy_setopt(easy, CURLOPT_UPLOAD, 0L);
69 (void)curl_easy_setopt(easy, CURLOPT_CUSTOMREQUEST, SendBackpressureCommand().c_str());
71 case curl::fuzzer::proto::SMTP_PROBE_DEFAULT:
86 const curl::fuzzer::proto::Scenario* script =
scenario();
87 recipient_count_ = script ==
nullptr ? 0
89 static_cast<std::size_t
>(script->mail_rcpt_size()));
90 rcpt_command_count_ = 0;
91 auth_exchange_active_ =
false;
92 tls_upgrade_started_ =
false;
97bool SmtpMockServer::IsStartTlsMode()
const {
98 return mode_ == curl::fuzzer::proto::SMTP_PROBE_STARTTLS_TRY ||
99 mode_ == curl::fuzzer::proto::SMTP_PROBE_STARTTLS_REQUIRE ||
100 mode_ == curl::fuzzer::proto::SMTP_PROBE_STARTTLS_FILTER_ALLOCATION_FAILURE;
107bool SmtpMockServer::AllowsRecipientFailures()
const {
108 const curl::fuzzer::proto::Scenario* script =
scenario();
109 if (script ==
nullptr) {
112 for (
int index = script->options_size() - 1; index >= 0; --index) {
113 const auto& option = script->options(index);
114 if (option.option_id() != curl::fuzzer::proto::CURLOPT_MAIL_RCPT_ALLOWFAILS) {
117 if (option.value_case() == curl::fuzzer::proto::SetOption::kBoolValue) {
118 return option.bool_value();
120 if (option.value_case() == curl::fuzzer::proto::SetOption::kUintValue) {
121 return option.uint_value() != 0;
133int SmtpMockServer::FirstReplyCode(std::string_view response) {
134 std::size_t line_start = 0;
135 while (line_start < response.size()) {
136 const std::size_t newline = response.find(
'\n', line_start);
137 if (newline == std::string_view::npos) {
140 const std::size_t line_size = newline - line_start + 1;
142 const unsigned char first =
static_cast<unsigned char>(response[line_start]);
143 const unsigned char second =
static_cast<unsigned char>(response[line_start + 1]);
144 const unsigned char third =
static_cast<unsigned char>(response[line_start + 2]);
145 if (first >=
'0' && first <=
'9' && second >=
'0' && second <=
'9' && third >=
'0' && third <=
'9' &&
146 response[line_start + 3] ==
' ') {
147 return 100 * (first -
'0') + 10 * (second -
'0') + (third -
'0');
150 line_start = newline + 1;
164 const std::string_view verb =
FirstToken(command);
165 const bool is_auth_continuation_reply = auth_exchange_active_;
168 if (response ==
nullptr) {
172 const int response_code = FirstReplyCode(*response);
175 if (
TokenEquals(verb,
"STARTTLS") && IsStartTlsMode() && response_code == 220) {
176 if (response_written && mode_ == curl::fuzzer::proto::SMTP_PROBE_STARTTLS_FILTER_ALLOCATION_FAILURE) {
177 allocation_fault_injection::ArmNextAllocationFailure();
179 tls_upgrade_started_ =
true;
188 auth_exchange_active_ = response_code == 334;
189 }
else if (is_auth_continuation_reply) {
193 auth_exchange_active_ =
true;
194 }
else if (response_code != 334) {
195 auth_exchange_active_ =
false;
200 if (response_code == 354) {
203 }
else if (response_code > 0) {
212 ++rcpt_command_count_;
220 const bool final_rcpt_error =
221 TokenEquals(verb,
"RCPT") && (rcpt_command_count_ >= recipient_count_ || !AllowsRecipientFailures());
222 const bool starttls_denial =
TokenEquals(verb,
"STARTTLS") && IsStartTlsMode();
223 const bool terminal_error = response_code >= 400 && !
TokenEquals(verb,
"EHLO") && !starttls_denial &&
224 !is_auth_continuation_reply && (!
TokenEquals(verb,
"RCPT") || final_rcpt_error);
225 if (terminal_error) {
235 if (!tls_upgrade_started_) {
242#if defined(PROTO_FUZZER_OBSERVE_STATE)
245 (void)smtp_observation;
virtual void ObserveState()
Default no-op for peers without protocol-specific observations.
const std::string & control_transcript() const
Expose the bounded command capture from the most recent drive so tests can verify protocol states wit...
bool WriteNextReply()
Consume the next scripted reply.
const curl::fuzzer::proto::Scenario * scenario() const
Borrowed scenario for the synchronous drive, or nullptr outside RunLoop().
void BeginOpaqueDrain()
Enter an opaque drain after a transport upgrade.
void ShutdownControlWrite()
Half-close the server-to-client direction while retaining the read half.
static std::string_view FirstToken(std::string_view line)
Remove optional leading horizontal whitespace and return the first token without allocating.
const std::string * NextControlReply()
Return the next command-aligned response within the shared response-count budget.
const std::string & uploaded_data() const
Expose a bounded prefix of bytes drained during the most recent body phase while continuing to drain ...
static bool TokenEquals(std::string_view token, std::string_view expected_uppercase)
Fold only ASCII lowercase command letters.
bool WriteControlBytes(std::string_view bytes)
Keep failed peer writes local to the mock.
void BeginMarkerBody(std::string_view terminator)
Enter marker-terminated body mode.
SmtpMockServer(curl::fuzzer::proto::SmtpProbeMode mode=curl::fuzzer::proto::SMTP_PROBE_DEFAULT)
Begin without a borrowed protobuf or open socketpair.
~SmtpMockServer() override
Release the server-owned socketpair half after curl has returned its client descriptor.
std::unique_ptr< MockConnection > CreateConnection() override
Create the default plaintext transport for the SMTP exchange.
void HandleLine(std::string_view line) override
Advance one reply for every complete command.
void ResetProtocolState() override
Reset SMTP's recipient bookkeeping for a new drive.
void ConfigureClient(CURL *easy) const
Apply only closed, harness-owned client controls.
void ObserveState() override
Retain protocol outcomes in coverage and introspector builds.
void QueueShutdownReply() override
Queue a 221 before the shared transport half-closes.
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...
Shared complexity limits for structured scenarios.
constexpr std::size_t kMaxMailRecipients
SMTP envelope recipients are schema-native because CURLOPT_MAIL_RCPT is a curl_slist.
Command-aware in-process peer for curl's SMTP state machine.