SMTP: Add SMTP_IN_BDAT state

Initially this reused SMTP_IN_DATA, but separating into SMTP_IN_BDAT
to avoid spurious EndData() calls upon a server's reply. The client
should usually continue to send the full in-flight chunk still.
This commit is contained in:
Arne Welzel 2024-01-23 15:03:37 +01:00
parent fbb1a57945
commit 9a510b8035
2 changed files with 14 additions and 13 deletions

View file

@ -84,7 +84,7 @@ void SMTP_Analyzer::Undelivered(uint64_t seq, int len, bool is_orig) {
Unexpected(is_orig, "content gap", buf_len, buf); Unexpected(is_orig, "content gap", buf_len, buf);
if ( state == detail::SMTP_IN_DATA ) { if ( state == detail::SMTP_IN_DATA || state == detail::SMTP_CMD_BDAT ) {
// Record the SMTP data gap and terminate the // Record the SMTP data gap and terminate the
// ongoing mail transaction. // ongoing mail transaction.
if ( mail ) if ( mail )
@ -202,7 +202,7 @@ void SMTP_Analyzer::ProcessLine(int length, const char* line, bool orig) {
expect_recver = true; expect_recver = true;
} }
else if ( state == detail::SMTP_IN_DATA && ! bdat ) { else if ( state == detail::SMTP_IN_DATA ) {
// Check "." for end of data for non-BDAT transfers. // Check "." for end of data for non-BDAT transfers.
expect_recver = false; // ?? MAY server respond to mail data? expect_recver = false; // ?? MAY server respond to mail data?
@ -591,21 +591,21 @@ void SMTP_Analyzer::UpdateState(int cmd_code, int reply_code, bool orig) {
UnexpectedCommand(cmd_code, reply_code); UnexpectedCommand(cmd_code, reply_code);
assert(bdat); assert(bdat);
state = detail::SMTP_IN_DATA; state = detail::SMTP_IN_BDAT;
break; break;
case 250: break; // server accepted BDAT transfer. case 250: break; // server accepted BDAT transfer.
case 421: state = detail::SMTP_QUIT; break; case 421:
case 500: case 500:
case 501: case 501:
case 503: case 503:
case 451: case 451:
case 554: case 554:
// Client may continue sending chunks if pipelined. We don't // Client will continue completing the inflight chunk no matter
// call EndData() here as it might be interesting what the // what the server replies, so we don't call EndData() here as
// client does send, even if the server isn't accepting it. // it might be interesting what the client does actually send,
// even if the server isn't accepting it.
break; break;
default: default:
@ -618,7 +618,7 @@ void SMTP_Analyzer::UpdateState(int cmd_code, int reply_code, bool orig) {
case detail::SMTP_CMD_END_OF_DATA: case detail::SMTP_CMD_END_OF_DATA:
switch ( reply_code ) { switch ( reply_code ) {
case 0: case 0:
if ( st != detail::SMTP_IN_DATA ) if ( st != detail::SMTP_IN_DATA && st != detail::SMTP_IN_BDAT )
UnexpectedCommand(cmd_code, reply_code); UnexpectedCommand(cmd_code, reply_code);
EndData(); EndData();
state = detail::SMTP_AFTER_DATA; state = detail::SMTP_AFTER_DATA;
@ -875,7 +875,7 @@ bool SMTP_Analyzer::ProcessBdatArg(int arg_len, const char* arg, bool orig) {
if ( ! bdat ) { if ( ! bdat ) {
// This is the first BDAT chunk. // This is the first BDAT chunk.
BeginData(orig); BeginData(orig, detail::SMTP_IN_BDAT);
bdat = std::make_unique<detail::SMTP_BDAT_Analyzer>(Conn(), mail, zeek::BifConst::SMTP::bdat_max_line_length); bdat = std::make_unique<detail::SMTP_BDAT_Analyzer>(Conn(), mail, zeek::BifConst::SMTP::bdat_max_line_length);
} }
@ -885,8 +885,8 @@ bool SMTP_Analyzer::ProcessBdatArg(int arg_len, const char* arg, bool orig) {
return true; return true;
} }
void SMTP_Analyzer::BeginData(bool orig) { void SMTP_Analyzer::BeginData(bool orig, detail::SMTP_State new_state) {
state = detail::SMTP_IN_DATA; state = new_state;
skip_data = false; // reset the flag at the beginning of the mail skip_data = false; // reset the flag at the beginning of the mail
if ( mail != nullptr ) { if ( mail != nullptr ) {
Weird("smtp_nested_mail_transaction"); Weird("smtp_nested_mail_transaction");

View file

@ -35,6 +35,7 @@ enum SMTP_State {
SMTP_QUIT, // 10: after QUIT SMTP_QUIT, // 10: after QUIT
SMTP_AFTER_GAP, // 11: after a gap is detected SMTP_AFTER_GAP, // 11: after a gap is detected
SMTP_GAP_RECOVERY, // 12: after the first reply after a gap SMTP_GAP_RECOVERY, // 12: after the first reply after a gap
SMTP_IN_BDAT, // 13: receiving BDAT chunk via plain delivery
}; };
} // namespace detail } // namespace detail
@ -63,7 +64,7 @@ protected:
void UpdateState(int cmd_code, int reply_code, bool orig); void UpdateState(int cmd_code, int reply_code, bool orig);
void BeginData(bool orig); void BeginData(bool orig, detail::SMTP_State new_state = detail::SMTP_IN_DATA);
void EndData(); void EndData();
int ParseCmd(int cmd_len, const char* cmd); int ParseCmd(int cmd_len, const char* cmd);