38 namespace ClientProtocolImp
45struct GSmtp::ClientProtocolImp::AuthError :
public ClientProtocol::SmtpError
48 std::string str()
const ;
51class GSmtp::ClientProtocolImp::EhloReply
54 explicit EhloReply(
const ClientReply & ) ;
55 bool has(
const std::string & option )
const ;
66 const Config & config ,
bool in_secure_tunnel ) :
69 m_sasl(
std::make_unique<
GAuth::SaslClient>(secrets,sasl_client_config)) ,
71 m_in_secure_tunnel(in_secure_tunnel) ,
72 m_eightbit_warned(false) ,
73 m_binarymime_warned(false) ,
74 m_utf8_warned(false) ,
77 m_config.bdat_chunk_size = std::max( std::size_t(64U) , m_config.bdat_chunk_size ) ;
78 m_config.reply_size_limit = std::max( std::size_t(100U) , m_config.reply_size_limit ) ;
79 m_message_line.reserve( 200U ) ;
84 G_DEBUG(
"GSmtp::ClientProtocol::start" ) ;
87 m_message_state = MessageState() ;
88 m_message_state.ptr = message_in ;
89 m_message_p = message_in.lock().get() ;
90 m_message_state.selector = m_message_p->clientAccountSelector() ;
91 m_message_state.id = m_message_p->id().str() ;
94 m_done_signal.reset() ;
100 G_DEBUG(
"GSmtp::ClientProtocol::finish" ) ;
101 m_protocol.state = State::Quitting ;
102 send(
"QUIT\r\n"_sv ) ;
112 if( m_protocol.state == State::Data )
114 std::size_t n = sendContentLines() ;
117 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
120 m_protocol.state = State::SentDot ;
128 return m_done_signal ;
133 return m_filter_signal ;
140 m_protocol.reply_lines.push_back( rx ) ;
143 std::swap( lines , m_protocol.reply_lines ) ;
147 throw SmtpError(
"invalid response" ) ;
154 else if( m_protocol.replySize() > m_config.reply_size_limit )
156 throw SmtpError(
"overflow on input" ) ;
160 std::swap( lines , m_protocol.reply_lines ) ;
165bool GSmtp::ClientProtocol::applyEvent(
const ClientReply & reply )
167 using AuthError = ClientProtocolImp::AuthError ;
172 bool protocol_done = false ;
173 bool is_start_event = reply.
is( ClientReply::Value::Internal_start ) ;
174 if( m_protocol.state == State::Init && is_start_event )
177 m_protocol.state = State::Started ;
178 if( m_config.ready_timeout != 0U )
179 startTimer( m_config.ready_timeout ) ;
181 else if( m_protocol.state == State::Init && reply.
is(ClientReply::Value::ServiceReady_220) )
184 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: init -> ready" ) ;
185 m_protocol.state = State::ServiceReady ;
187 else if( m_protocol.state == State::ServiceReady && is_start_event )
190 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: ready -> sent-ehlo" ) ;
191 m_protocol.state = State::SentEhlo ;
194 else if( m_protocol.state == State::Started && reply.
is(ClientReply::Value::ServiceReady_220) )
197 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: start -> sent-ehlo" ) ;
198 m_protocol.state = State::SentEhlo ;
201 else if( m_protocol.state == State::MessageDone && is_start_event && m_session.ok(m_message_state.selector) )
204 m_protocol.state = State::Filtering ;
207 else if( m_protocol.state == State::MessageDone && is_start_event )
210 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: new account selector [" << m_message_state.selector <<
"]" ) ;
211 if( !m_config.try_reauthentication )
212 throw SmtpError(
"cannot switch client account" ) ;
213 m_protocol.state = m_session.secure ? State::SentTlsEhlo : State::SentEhlo ;
216 else if( m_protocol.state == State::SentEhlo && (
217 reply.
is(ClientReply::Value::SyntaxError_500) ||
218 reply.
is(ClientReply::Value::SyntaxError_501) ||
219 reply.
is(ClientReply::Value::NotImplemented_502) ) )
222 if( m_config.must_use_tls && !m_in_secure_tunnel )
223 throw SmtpError(
"tls is mandated but the server cannot do esmtp" ) ;
224 m_protocol.state = State::SentHelo ;
227 else if( ( m_protocol.state == State::SentEhlo ||
228 m_protocol.state == State::SentHelo ||
229 m_protocol.state == State::SentTlsEhlo ) &&
230 reply.
is(ClientReply::Value::Ok_250) )
233 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: ehlo reply \"" <<
G::Str::printable(reply.
text()) <<
"\"" ) ;
234 m_session = SessionState() ;
235 if( m_protocol.state != State::SentHelo )
237 ClientProtocolImp::EhloReply ehlo_reply( reply ) ;
238 m_session.server.has_starttls = m_protocol.state == State::SentEhlo && ehlo_reply.has(
"STARTTLS" ) ;
239 m_session.server.has_8bitmime = ehlo_reply.has(
"8BITMIME" ) ;
240 m_session.server.has_binarymime = ehlo_reply.has(
"BINARYMIME" ) ;
241 m_session.server.has_chunking = ehlo_reply.has(
"CHUNKING" ) ;
242 m_session.server.auth_mechanisms = ehlo_reply.values(
"AUTH" ) ;
243 m_session.server.has_auth = !m_session.server.auth_mechanisms.empty() ;
244 m_session.server.has_pipelining = ehlo_reply.has(
"PIPELINING" ) ;
245 m_session.server.has_smtputf8 = ehlo_reply.has(
"SMTPUTF8" ) ;
246 m_session.secure = m_protocol.state == State::SentTlsEhlo || m_in_secure_tunnel ;
250 m_session.auth_mechanism = m_sasl->mechanism( m_session.server.auth_mechanisms , m_message_state.selector ) ;
253 if( !m_sasl->validSelector( m_message_state.selector ) )
255 throw BadSelector( std::string(
"selector [").append(m_message_state.selector).append(1U,
']') ) ;
257 else if( !m_session.secure && m_config.must_use_tls )
259 if( !m_session.server.has_starttls )
260 throw SmtpError(
"tls is mandated but the server cannot do starttls" ) ;
261 m_protocol.state = State::StartTls ;
262 send(
"STARTTLS\r\n"_sv ) ;
264 else if( !m_session.secure && m_config.use_starttls_if_possible && m_session.server.has_starttls )
266 m_protocol.state = State::StartTls ;
267 send(
"STARTTLS\r\n"_sv ) ;
269 else if( m_sasl->mustAuthenticate(m_message_state.selector) && m_session.server.has_auth && m_session.auth_mechanism.empty() )
271 std::string e =
"cannot do authentication: check for a compatible client secret" ;
272 if( !m_message_state.selector.empty() )
273 e.append(
" with selector [").append(
G::Str::printable(m_message_state.selector)).append(1U,
']') ;
274 throw SmtpError( e ) ;
276 else if( m_sasl->mustAuthenticate(m_message_state.selector) && !m_session.server.has_auth )
278 throw SmtpError(
"authentication is not supported by the remote smtp server" ) ;
280 else if( m_sasl->mustAuthenticate(m_message_state.selector) )
282 m_protocol.state = State::Auth ;
284 std::string rsp_data = rsp.data.empty() ? std::string() :
std::string(1U,
' ').append(
G::Base64::encode(rsp.data)) ;
285 send(
"AUTH "_sv , m_session.auth_mechanism , rsp_data ,
"\r\n"_sv , rsp.sensitive ) ;
289 m_protocol.state = State::Filtering ;
293 else if( m_protocol.state == State::StartTls && reply.
is(ClientReply::Value::ServiceReady_220) )
296 m_sender.protocolSend( {} , 0U , true ) ;
298 else if( m_protocol.state == State::StartTls && reply.
is(ClientReply::Value::NotAvailable_454) )
303 else if( m_protocol.state == State::StartTls && reply.
is(ClientReply::Value::Internal_secure) )
306 m_protocol.state = State::SentTlsEhlo ;
309 else if( m_protocol.state == State::Auth && reply.
is(ClientReply::Value::Challenge_334) &&
320 else if( m_protocol.state == State::Auth && reply.
is(ClientReply::Value::Challenge_334) )
325 else if( m_protocol.state == State::Auth && reply.
positive() )
328 m_session.authenticated = true ;
329 m_session.auth_selector = m_message_state.selector ;
330 G_LOG(
"GSmtp::ClientProtocol::applyEvent: successful authentication with remote server "
331 << (m_session.secure?
"over tls ":
"") << m_sasl->info() ) ;
332 m_protocol.state = State::Filtering ;
335 else if( m_protocol.state == State::Auth && !reply.
positive() && m_sasl->next() )
338 G_LOG(
"GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl,reply).str()
339 <<
": trying [" <<
G::Str::lower(m_sasl->mechanism()) <<
"]" ) ;
340 m_session.auth_mechanism = m_sasl->mechanism() ;
342 std::string rsp_data = rsp.data.empty() ? std::string() :
std::string(1U,
' ').append(
G::Base64::encode(rsp.data)) ;
343 send(
"AUTH "_sv , m_session.auth_mechanism , rsp_data ,
"\r\n"_sv , rsp.sensitive ) ;
345 else if( m_protocol.state == State::Auth && !reply.
positive() && !m_config.authentication_fallthrough )
348 throw AuthError( *m_sasl , reply ) ;
350 else if( m_protocol.state == State::Auth && !reply.
positive() )
353 G_ASSERT( !m_session.authenticated ) ;
354 G_WARNING(
"GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl,reply).str() <<
": continuing" ) ;
355 m_protocol.state = State::Filtering ;
358 else if( m_protocol.state == State::Filtering && reply.
is(ClientReply::Value::Internal_filter_abandon) )
361 m_protocol.state = State::MessageDone ;
362 raiseDoneSignal( reply.
doneCode() , std::string() ) ;
364 else if( m_protocol.state == State::Filtering && reply.
is(ClientReply::Value::Internal_filter_error) )
367 m_protocol.state = State::MessageDone ;
370 else if( m_protocol.state == State::Filtering && reply.
is(ClientReply::Value::Internal_filter_ok) )
373 std::string reason = checkSendable() ;
374 if( !reason.empty() )
376 m_protocol.state = State::MessageDone ;
377 raiseDoneSignal( 0 ,
"failed" , reason ) ;
381 m_protocol.state = State::SentMail ;
385 else if( m_protocol.state == State::SentMail && reply.
is(ClientReply::Value::Ok_250) )
388 m_protocol.state = State::SentRcpt ;
391 else if( m_protocol.state == State::SentMail && !reply.
positive() )
394 m_protocol.state = State::MessageDone ;
397 else if( m_protocol.state == State::SentRcpt && m_message_state.to_index < message().toCount() )
402 m_message_state.to_accepted++ ;
404 m_message_state.to_rejected.push_back( message().to(m_message_state.to_index-1U) ) ;
407 else if( m_protocol.state == State::SentRcpt )
413 m_message_state.to_accepted++ ;
415 m_message_state.to_rejected.push_back( message().to(m_message_state.to_index-1U) ) ;
417 if( ( m_config.must_accept_all_recipients && m_message_state.to_accepted < message().toCount() ) || m_message_state.to_accepted == 0U )
419 m_protocol.state = State::SentDataStub ;
420 send(
"RSET\r\n"_sv ) ;
422 else if( ( message().bodyType() == BodyType::BinaryMime ||
G::Test::enabled(
"smtp-client-prefer-bdat") ) &&
423 m_session.server.has_binarymime && m_session.server.has_chunking )
426 m_message_state.content_size = message().contentSize() ;
427 std::string content_size_str = std::to_string( m_message_state.content_size ) ;
429 bool one_chunk = (m_message_state.content_size+5U) <= m_config.bdat_chunk_size ;
432 m_protocol.state = State::SentBdatLast ;
433 sendBdatAndChunk( m_message_state.content_size , content_size_str ,
true ) ;
437 m_protocol.state = State::SentBdatMore ;
439 m_message_state.chunk_data_size = m_config.bdat_chunk_size ;
440 m_message_state.chunk_data_size_str = std::to_string(m_message_state.chunk_data_size) ;
442 bool last = sendBdatAndChunk( m_message_state.chunk_data_size , m_message_state.chunk_data_size_str ,
false ) ;
444 m_protocol.state = State::SentBdatLast ;
449 m_protocol.state = State::SentData ;
450 send(
"DATA\r\n"_sv ) ;
453 else if( m_protocol.state == State::SentData && reply.
is(ClientReply::Value::OkForData_354) )
456 m_protocol.state = State::Data ;
457 std::size_t n = sendContentLines() ;
458 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
461 m_protocol.state = State::SentDot ;
465 else if( m_protocol.state == State::SentDataStub )
468 m_protocol.state = State::MessageDone ;
469 std::string how_many = m_config.must_accept_all_recipients ? std::string(
"one or more") :
std::string(
"all") ;
470 raiseDoneSignal( reply.
doneCode() , how_many +
" recipients rejected" ) ;
472 else if( m_protocol.state == State::SentBdatMore )
477 bool last = sendBdatAndChunk( m_message_state.chunk_data_size , m_message_state.chunk_data_size_str ,
false ) ;
479 m_protocol.state = State::SentBdatLast ;
486 else if( m_protocol.state == State::SentDot || m_protocol.state == State::SentBdatLast )
489 m_protocol.state = State::MessageDone ;
490 m_message_line.clear() ;
491 m_message_buffer.clear() ;
492 if( reply.
positive() && m_message_state.to_accepted < message().toCount() )
493 raiseDoneSignal( 0 ,
"one or more recipients rejected" ) ;
497 else if( m_protocol.state == State::Quitting && reply.
value() == 221 )
500 protocol_done = true ;
502 else if( is_start_event )
509 G_WARNING(
"GSmtp::ClientProtocol: client protocol: "
511 throw SmtpError(
"unexpected response" , reply.
errorText() ) ;
513 return protocol_done ;
521 G_ASSERT( !m_message_state.ptr.expired() ) ;
522 G_ASSERT( m_message_p !=
nullptr ) ;
523 if( m_message_state.ptr.expired() || m_message_p ==
nullptr )
524 throw SmtpError(
"invalid internal state" ) ;
526 return *m_message_p ;
534void GSmtp::ClientProtocol::onTimeout()
536 if( m_protocol.state == State::Started )
539 G_WARNING(
"GSmtp::ClientProtocol: timeout: no greeting from remote server after "
540 << m_config.ready_timeout <<
"s: continuing" ) ;
541 m_protocol.state = State::SentEhlo ;
544 else if( m_protocol.state == State::Filtering )
546 throw SmtpError(
"filtering timeout" ) ;
548 else if( m_protocol.state == State::Data )
550 throw SmtpError(
"flow-control timeout after " +
G::Str::fromUInt(m_config.response_timeout) +
"s" ) ;
554 throw SmtpError(
"response timeout after " +
G::Str::fromUInt(m_config.response_timeout) +
"s" ) ;
558void GSmtp::ClientProtocol::startFiltering()
560 G_ASSERT( m_protocol.state == State::Filtering ) ;
561 m_filter_signal.emit() ;
566 if( result == Filter::Result::ok )
571 else if( result == Filter::Result::abandon )
583void GSmtp::ClientProtocol::raiseDoneSignal(
int response_code ,
const std::string & response ,
584 const std::string & reason )
586 if( !response.empty() && response_code == 0 )
587 G_WARNING(
"GSmtp::ClientProtocol: smtp client protocol: " << response ) ;
589 m_message_p = nullptr ;
592 m_done_signal.emit( { response_code , response , reason ,
G::StringArray(m_message_state.to_rejected) } ) ;
595bool GSmtp::ClientProtocol::endOfContent()
597 return !message().contentStream().good() ;
600std::string GSmtp::ClientProtocol::checkSendable()
602 const bool eightbitmime_mismatch =
603 message().bodyType() == BodyType::EightBitMime &&
604 !m_session.server.has_8bitmime ;
606 const bool utf8_mismatch =
607 message().utf8Mailboxes() &&
608 !m_session.server.has_smtputf8 ;
610 const bool binarymime_mismatch =
611 message().bodyType() == BodyType::BinaryMime &&
612 !( m_session.server.has_binarymime && m_session.server.has_chunking ) ;
614 if( eightbitmime_mismatch && m_config.eightbit_strict )
617 return "cannot send 8-bit message to 7-bit server" ;
619 else if( binarymime_mismatch && m_config.binarymime_strict )
622 return "cannot send binarymime message to a non-chunking server" ;
624 else if( utf8_mismatch && m_config.smtputf8_strict )
627 return "cannot send utf8 message to non-smtputf8 server" ;
631 if( eightbitmime_mismatch && !m_eightbit_warned )
633 m_eightbit_warned = true ;
634 G_WARNING(
"GSmtp::ClientProtocol::checkSendable: sending an eight-bit message "
635 "to a server that has not advertised the 8BITMIME extension" ) ;
637 if( binarymime_mismatch && !m_binarymime_warned )
639 m_binarymime_warned = true ;
640 G_WARNING(
"GSmtp::ClientProtocol::checkSendable: sending a binarymime message "
641 "to a server that has not advertised the BINARYMIME/CHUNKING extension" ) ;
643 if( utf8_mismatch && !m_utf8_warned )
645 m_utf8_warned = true ;
646 G_WARNING(
"GSmtp::ClientProtocol::checkSendable: sending a message with utf8 mailbox names"
647 " to a server that has not advertised the SMTPUTF8 extension" ) ;
649 return std::string() ;
653bool GSmtp::ClientProtocol::sendMailFrom()
655 bool use_bdat = false ;
656 std::string mail_from_tail = message().from() ;
657 mail_from_tail.append( 1U ,
'>' ) ;
659 if( message().bodyType() == BodyType::SevenBit )
661 if( m_session.server.has_8bitmime )
662 mail_from_tail.append(
" BODY=7BIT" ) ;
664 else if( message().bodyType() == BodyType::EightBitMime )
666 if( m_session.server.has_8bitmime )
667 mail_from_tail.append(
" BODY=8BITMIME" ) ;
669 else if( message().bodyType() == BodyType::BinaryMime )
671 if( m_session.server.has_binarymime && m_session.server.has_chunking )
673 mail_from_tail.append(
" BODY=BINARYMIME" ) ;
678 if( m_session.server.has_smtputf8 && message().utf8Mailboxes() )
680 mail_from_tail.append(
" SMTPUTF8" ) ;
683 if( m_session.authenticated )
685 if( m_config.anonymous )
687 mail_from_tail.append(
" AUTH=<>" ) ;
689 else if( message().fromAuthOut().empty() && !m_sasl->id().empty() )
693 mail_from_tail.append(
" AUTH=" ) ;
696 else if( m_session.authenticated &&
G::Xtext::valid(message().fromAuthOut()) )
698 mail_from_tail.append(
" AUTH=" ) ;
699 mail_from_tail.append( message().fromAuthOut() ) ;
703 mail_from_tail.append(
" AUTH=<>" ) ;
707 if( m_config.pipelining && m_session.server.has_pipelining )
716 std::string commands ;
717 commands.reserve( 2000U ) ;
718 commands.append(
"MAIL FROM:<").append(mail_from_tail).append(
"\r\n",2U) ;
719 const std::size_t n = message().toCount() ;
720 for( std::size_t i = 0U ; i < n ; i++ )
721 commands.append(
"RCPT TO:<").append(message().to(i)).append(
">\r\n",3U) ;
722 m_message_state.to_index = 0 ;
723 sendCommandLines( commands ) ;
727 send(
"MAIL FROM:<"_sv , mail_from_tail ,
"\r\n"_sv ) ;
732void GSmtp::ClientProtocol::sendRcptTo()
734 if( m_config.pipelining && m_session.server.has_pipelining )
736 m_message_state.to_index++ ;
740 G_ASSERT( m_message_state.to_index < message().toCount() ) ;
741 std::string to = message().to( m_message_state.to_index++ ) ;
742 send(
"RCPT TO:<"_sv , to ,
">\r\n"_sv ) ;
746std::size_t GSmtp::ClientProtocol::sendContentLines()
750 m_message_line.resize( 1U ) ;
751 m_message_line.at(0) =
'.' ;
753 std::size_t line_count = 0U ;
754 while( sendNextContentLine(m_message_line) )
760bool GSmtp::ClientProtocol::sendNextContentLine( std::string & line )
770 G_ASSERT( !line.empty() && line.at(0) ==
'.' ) ;
774 m_config.crlf_only ? G::Str::Eol::CrLf : G::Str::Eol::Cr_Lf_CrLf ,
777 line.append(
"\r\n" , 2U ) ;
778 ok = sendContentLineImp( line , line.at(1U) ==
'.' ? 0U : 1U ) ;
783void GSmtp::ClientProtocol::sendEhlo()
785 send(
"EHLO "_sv , m_config.thishost_name ,
"\r\n"_sv ) ;
788void GSmtp::ClientProtocol::sendHelo()
790 send(
"HELO "_sv , m_config.thishost_name ,
"\r\n"_sv ) ;
793void GSmtp::ClientProtocol::sendEot()
795 sendImp(
".\r\n"_sv ) ;
801 sendImp( s , rsp.sensitive ? 0U : std::string::npos ) ;
804void GSmtp::ClientProtocol::sendCommandLines(
const std::string & lines )
806 sendImp( lines.data() ) ;
816 std::string line = std::string(s0.data(),s0.size()).append(s1.data(),s1.size()).append(s2.data(),s2.size()).append(s3.data(),s3.size()) ;
817 sendImp( line , ( s2_sensitive && !s2.empty() ) ? (s0.size()+s1.size()) : std::string::npos ) ;
820bool GSmtp::ClientProtocol::sendBdatAndChunk( std::size_t size ,
const std::string & size_str ,
bool last )
830 std::size_t buffer_size = size + (last?12U:7U) + size_str.size() ;
831 std::size_t eolpos = (last?10U:5U) + size_str.size() ;
832 std::size_t datapos = eolpos + 2U ;
833 std::size_t margin = last ? 0U : 10U ;
835 m_message_buffer.resize( buffer_size + margin ) ;
836 char * out = &m_message_buffer[0] + margin ;
838 std::memcpy( out ,
"BDAT " , 5U ) ;
839 std::memcpy( out+5U , size_str.data() , size_str.size() ) ;
841 std::memcpy( out+5U+size_str.size() ,
" LAST" , 5U ) ;
842 std::memcpy( out+eolpos ,
"\r\n" , 2U ) ;
844 G_ASSERT( buffer_size > datapos ) ;
845 G_ASSERT( (out+datapos) < (&m_message_buffer[0]+m_message_buffer.size()) ) ;
846 message().contentStream().read( out+datapos , buffer_size-datapos ) ;
847 std::streamsize gcount = message().contentStream().gcount() ;
849 G_ASSERT( gcount >= 0 ) ;
851 std::size_t nread =
static_cast<std::size_t
>( gcount ) ;
853 bool eof = (datapos+nread) < buffer_size ;
859 std::string n = std::to_string( nread ) ;
860 std::size_t cmdsize = 12U + n.size() ;
861 out = out + datapos - cmdsize ;
863 G_ASSERT( n.size() <= size_str.size() ) ;
864 G_ASSERT( out >= &m_message_buffer[0] ) ;
865 std::memcpy( out ,
"BDAT " , 5U ) ;
866 std::memcpy( out+5U , n.data() , n.size() ) ;
867 std::memcpy( out+5U+n.size(),
" LAST\r\n" , 7U ) ;
870 sendChunkImp( out , datapos+nread ) ;
876void GSmtp::ClientProtocol::sendChunkImp(
const char * p , std::size_t n )
880 if( m_config.response_timeout != 0U )
881 startTimer( m_config.response_timeout ) ;
885 std::size_t pos = sv.find(
"\r\n"_sv ) ;
889 G::string_view end = count.size() == 1U && count[0] ==
'1' ?
"]"_sv :
"s]"_sv ;
890 G_LOG(
"GSmtp::ClientProtocol: tx>>: \"" << cmd <<
"\" [" << count <<
" byte" << end ) ;
893 m_sender.protocolSend( sv , 0U ,
false ) ;
896bool GSmtp::ClientProtocol::sendContentLineImp(
const std::string & line , std::size_t offset )
898 bool all_sent = m_sender.protocolSend( line , offset ,
false ) ;
899 if( !all_sent && m_config.response_timeout != 0U )
900 startTimer( m_config.response_timeout ) ;
904bool GSmtp::ClientProtocol::sendImp(
G::string_view line , std::size_t sensitive_from )
906 G_ASSERT( line.size() > 2U && line.rfind(
'\n') == (line.size()-1U) ) ;
908 if( m_protocol.state == State::Quitting )
910 else if( m_config.response_timeout != 0U )
911 startTimer( m_config.response_timeout ) ;
913 std::size_t pos = 0U ;
916 if( sensitive_from == std::string::npos || (pos+f.size()) < sensitive_from )
917 G_LOG(
"GSmtp::ClientProtocol: tx>>: "
919 else if( pos >= sensitive_from )
920 G_LOG(
"GSmtp::ClientProtocol: tx>>: [response not logged]" ) ;
922 G_LOG(
"GSmtp::ClientProtocol: tx>>: "
923 "\"" <<
G::Str::printable(f().substr(0U,sensitive_from-pos)) <<
" [not logged]\"" ) ;
926 return m_sender.protocolSend( line , 0U ,
false ) ;
931GSmtp::ClientProtocolImp::EhloReply::EhloReply(
const ClientReply & reply ) :
934 G_ASSERT( reply.is(ClientReply::Value::Ok_250) ) ;
937bool GSmtp::ClientProtocolImp::EhloReply::has(
const std::string & option )
const
939 return m_reply.text().find(std::string(1U,
'\n').append(option)) != std::string::npos ;
942G::StringArray GSmtp::ClientProtocolImp::EhloReply::values(
const std::string & option )
const
945 std::string text = m_reply.text() ;
946 std::size_t start_pos = text.find( std::string(1U,
'\n').append(option).append(1U,
' ') ) ;
947 if( start_pos != std::string::npos )
949 std::size_t end_pos = text.find(
'\n' , start_pos+1U ) ;
950 std::size_t size = end_pos == std::string::npos ? end_pos : ( end_pos - start_pos ) ;
952 G_ASSERT( result.at(0U) == option ) ;
953 if( !result.empty() ) result.erase( result.begin() ) ;
960std::size_t GSmtp::ClientProtocol::Protocol::replySize()
const
962 return std::accumulate( reply_lines.begin() , reply_lines.end() , std::size_t(0U) ,
963 [](std::size_t n,
const std::string& s){return n+s.size();} ) ;
968GSmtp::ClientProtocol::Config::Config()
974 const ClientReply & reply ) :
975 SmtpError(
"authentication failed " + sasl.info() +
": [" +
G::Str::printable(reply.text()) +
"]" )
979std::string GSmtp::ClientProtocolImp::AuthError::str()
const
981 return std::string( what() ) ;
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret.
A class that implements the client-side SASL challenge/response concept.
Response initialResponse(G::string_view selector, std::size_t limit=0U) const
Returns an optional initial response.
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
An interface used by ClientProtocol to send protocol messages.
void start(std::weak_ptr< GStore::StoredMessage >)
Starts transmission of the given message.
void secure()
To be called when the secure socket protocol has been successfully established.
void finish()
Called after the last message has been sent.
ClientProtocol(GNet::ExceptionSink, Sender &sender, const GAuth::SaslClientSecrets &secrets, const std::string &sasl_client_config, const Config &config, bool in_secure_tunnel)
Constructor.
void filterDone(Filter::Result result, const std::string &response, const std::string &reason)
To be called when the Filter interface has done its thing.
G::Slot::Signal< const DoneInfo & > & doneSignal() noexcept
Returns a signal that is raised once the protocol has finished with a given message.
G::Slot::Signal & filterSignal() noexcept
Returns a signal that is raised when the protocol needs to do message filtering.
void sendComplete()
To be called when a blocked connection becomes unblocked.
bool apply(const std::string &rx)
Called on receipt of a line of text from the remote server.
Encapsulates SMTP replies from a remote client, or replies from a client filter, or the result of a T...
std::string errorText() const
Returns the empty string if positiveCompletion() or non-empty text() or "error".
static ClientReply secure()
Factory function for Internal_secure.
static ClientReply filterAbandon()
Factory function for Internal_filter_abandon.
int value() const
Returns the numeric value of the reply.
std::string text() const
Returns the text of the reply, with some whitespace normalisation and no tabs.
static ClientReply start()
Factory function for Internal_start.
bool positive() const
Returns true if value() is between 100 and 399.
std::string reason() const
Returns the filter-reason text from a filterError() reply or the empty string.
static ClientReply filterOk()
Factory function for Internal_filter_ok.
static bool complete(const G::StringArray &)
Returns true if the reply text is valid() and complete.
bool is(Value v) const
Returns true if the value() is as given.
static ClientReply filterError(const std::string &response, const std::string &filter_reason)
Factory function for Internal_filter_error.
int doneCode() const
Returns -1 for filterAbandon() or -2 for filterError() or zero if less than 100 or value().
static bool valid(const G::StringArray &)
Returns true if the reply text is syntactivally valid but possibly incomplete.
An abstract interface for messages which have come from the store.
static std::string decode(string_view, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
static bool valid(string_view, bool strict=true)
Returns true if the string is a valid base64 encoding, possibly allowing for embedded newlines,...
static std::string encode(string_view, string_view line_break={})
Encodes the given string, optionally inserting line-breaks to limit the line length.
static bool atVerbose() noexcept
Returns at(Severity::InfoVerbose).
static string_view headView(string_view in, std::size_t pos, string_view default_={}) noexcept
Like head() but returning a view into the input string.
static std::istream & readLine(std::istream &stream, std::string &result, string_view eol={}, bool pre_erase_result=true, std::size_t limit=0U)
Reads a line from the stream using the given line terminator, which may be multi-character.
static void splitIntoTokens(const std::string &in, StringArray &out, string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
static std::string lower(string_view)
Returns a copy of 's' in which all seven-bit upper-case characters have been replaced by lower-case c...
static std::string fromUInt(unsigned int ui)
Converts unsigned int 'ui' to a string.
static std::string printable(const std::string &in, char escape='\\')
Returns a printable representation of the given input string, using chacter code ranges 0x20 to 0x7e ...
static string_view ws() noexcept
Returns a string of standard whitespace characters.
A zero-copy string field iterator where the field separators are short fixed strings.
bool last() const noexcept
Returns true if the current field is the last.
A zero-copy string token iterator where the token separators are runs of whitespace characters,...
static bool enabled() noexcept
Returns true if test features are enabled.
static std::string encode(string_view)
Encodes the given string.
static bool valid(string_view, bool strict=false)
Returns true if a valid encoding, or empty.
A class like c++17's std::string_view.
SASL authentication classes.
std::vector< std::string > StringArray
A std::vector of std::strings.
Result structure returned from GAuth::SaslClient::response.
A structure containing GSmtp::ClientProtocol configuration parameters.