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) ,
74 m_config.bdat_chunk_size = std::max( std::size_t(64U) , m_config.bdat_chunk_size ) ;
75 m_config.reply_size_limit = std::max( std::size_t(100U) , m_config.reply_size_limit ) ;
76 m_message_line.reserve( 200U ) ;
81 G_ASSERT( !ehlo.empty() ) ;
82 m_config.ehlo = ehlo ;
87 G_DEBUG(
"GSmtp::ClientProtocol::start" ) ;
90 m_message_state = MessageState() ;
91 m_message_state.ptr = message_in ;
92 m_message_p = message_in.lock().get() ;
93 m_message_state.selector = m_message_p->clientAccountSelector() ;
94 m_message_state.id = m_message_p->id().str() ;
97 m_done_signal.reset() ;
103 G_DEBUG(
"GSmtp::ClientProtocol::finish" ) ;
104 m_protocol.state = State::Quitting ;
105 send(
"QUIT\r\n"_sv ) ;
115 if( m_protocol.state == State::Data )
117 std::size_t n = sendContentLines() ;
120 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
123 m_protocol.state = State::SentDot ;
131 return m_done_signal ;
136 return m_filter_signal ;
143 m_protocol.reply_lines.push_back( rx ) ;
146 std::swap( lines , m_protocol.reply_lines ) ;
150 throw SmtpError(
"invalid response" ) ;
157 else if( m_protocol.replySize() > m_config.reply_size_limit )
159 throw SmtpError(
"overflow on input" ) ;
163 std::swap( lines , m_protocol.reply_lines ) ;
168bool GSmtp::ClientProtocol::applyEvent(
const ClientReply & reply )
170 using AuthError = ClientProtocolImp::AuthError ;
175 bool protocol_done = false ;
176 bool is_start_event = reply.
is( ClientReply::Value::Internal_start ) ;
177 if( m_protocol.state == State::Init && is_start_event )
180 m_protocol.state = State::Started ;
181 if( m_config.ready_timeout != 0U )
182 startTimer( m_config.ready_timeout ) ;
184 else if( m_protocol.state == State::Init && reply.
is(ClientReply::Value::ServiceReady_220) )
187 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: init -> ready" ) ;
188 m_protocol.state = State::ServiceReady ;
190 else if( m_protocol.state == State::ServiceReady && is_start_event )
193 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: ready -> sent-ehlo" ) ;
194 m_protocol.state = State::SentEhlo ;
197 else if( m_protocol.state == State::Started && reply.
is(ClientReply::Value::ServiceReady_220) )
200 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: start -> sent-ehlo" ) ;
201 m_protocol.state = State::SentEhlo ;
204 else if( m_protocol.state == State::MessageDone && is_start_event && m_session.ok(m_message_state.selector) )
207 m_protocol.state = State::Filtering ;
210 else if( m_protocol.state == State::MessageDone && is_start_event )
213 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: new account selector [" << m_message_state.selector <<
"]" ) ;
214 if( !m_config.try_reauthentication )
215 throw SmtpError(
"cannot switch client account" ) ;
216 m_protocol.state = m_session.secure ? State::SentTlsEhlo : State::SentEhlo ;
219 else if( m_protocol.state == State::SentEhlo && (
220 reply.
is(ClientReply::Value::SyntaxError_500) ||
221 reply.
is(ClientReply::Value::SyntaxError_501) ||
222 reply.
is(ClientReply::Value::NotImplemented_502) ) )
225 if( m_config.must_use_tls && !m_in_secure_tunnel )
226 throw SmtpError(
"tls is mandated but the server cannot do esmtp" ) ;
227 m_protocol.state = State::SentHelo ;
230 else if( ( m_protocol.state == State::SentEhlo ||
231 m_protocol.state == State::SentHelo ||
232 m_protocol.state == State::SentTlsEhlo ) &&
233 reply.
is(ClientReply::Value::Ok_250) )
236 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: ehlo reply \"" <<
G::Str::printable(reply.
text()) <<
"\"" ) ;
237 m_session = SessionState() ;
238 if( m_protocol.state != State::SentHelo )
240 ClientProtocolImp::EhloReply ehlo_reply( reply ) ;
241 m_session.server.has_starttls = m_protocol.state == State::SentEhlo && ehlo_reply.has(
"STARTTLS" ) ;
242 m_session.server.has_8bitmime = ehlo_reply.has(
"8BITMIME" ) ;
243 m_session.server.has_binarymime = ehlo_reply.has(
"BINARYMIME" ) ;
244 m_session.server.has_chunking = ehlo_reply.has(
"CHUNKING" ) ;
245 m_session.server.auth_mechanisms = ehlo_reply.values(
"AUTH" ) ;
246 m_session.server.has_auth = !m_session.server.auth_mechanisms.empty() ;
247 m_session.server.has_pipelining = ehlo_reply.has(
"PIPELINING" ) ;
248 m_session.server.has_smtputf8 = ehlo_reply.has(
"SMTPUTF8" ) ;
249 m_session.secure = m_protocol.state == State::SentTlsEhlo || m_in_secure_tunnel ;
253 m_session.auth_mechanism = m_sasl->mechanism( m_session.server.auth_mechanisms , m_message_state.selector ) ;
256 if( !m_sasl->validSelector( m_message_state.selector ) )
258 throw BadSelector( std::string(
"selector [").append(m_message_state.selector).append(1U,
']') ) ;
260 else if( !m_session.secure && m_config.must_use_tls )
262 if( !m_session.server.has_starttls )
263 throw SmtpError(
"tls is mandated but the server cannot do starttls" ) ;
264 m_protocol.state = State::StartTls ;
265 send(
"STARTTLS\r\n"_sv ) ;
267 else if( !m_session.secure && m_config.use_starttls_if_possible && m_session.server.has_starttls )
269 m_protocol.state = State::StartTls ;
270 send(
"STARTTLS\r\n"_sv ) ;
272 else if( m_sasl->mustAuthenticate(m_message_state.selector) && m_session.server.has_auth && m_session.auth_mechanism.empty() )
274 std::string e =
"cannot do authentication: check for a compatible client secret" ;
275 if( !m_message_state.selector.empty() )
276 e.append(
" with selector [").append(
G::Str::printable(m_message_state.selector)).append(1U,
']') ;
277 throw SmtpError( e ) ;
279 else if( m_sasl->mustAuthenticate(m_message_state.selector) && !m_session.server.has_auth )
281 throw SmtpError(
"authentication is not supported by the remote smtp server" ) ;
283 else if( m_sasl->mustAuthenticate(m_message_state.selector) )
285 m_protocol.state = State::Auth ;
287 std::string rsp_data = rsp.data.empty() ? std::string() :
std::string(1U,
' ').append(
G::Base64::encode(rsp.data)) ;
288 send(
"AUTH "_sv , m_session.auth_mechanism , rsp_data ,
"\r\n"_sv , rsp.sensitive ) ;
292 m_protocol.state = State::Filtering ;
296 else if( m_protocol.state == State::StartTls && reply.
is(ClientReply::Value::ServiceReady_220) )
299 m_sender.protocolSend( {} , 0U , true ) ;
301 else if( m_protocol.state == State::StartTls && reply.
is(ClientReply::Value::NotAvailable_454) )
306 else if( m_protocol.state == State::StartTls && reply.
is(ClientReply::Value::Internal_secure) )
309 m_protocol.state = State::SentTlsEhlo ;
312 else if( m_protocol.state == State::Auth && reply.
is(ClientReply::Value::Challenge_334) &&
323 else if( m_protocol.state == State::Auth && reply.
is(ClientReply::Value::Challenge_334) )
328 else if( m_protocol.state == State::Auth && reply.
positive() )
331 m_session.authenticated = true ;
332 m_session.auth_selector = m_message_state.selector ;
333 G_LOG(
"GSmtp::ClientProtocol::applyEvent: successful authentication with remote server "
334 << (m_session.secure?
"over tls ":
"") << m_sasl->info() ) ;
335 m_protocol.state = State::Filtering ;
338 else if( m_protocol.state == State::Auth && !reply.
positive() && m_sasl->next() )
341 G_LOG(
"GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl,reply).str()
342 <<
": trying [" <<
G::Str::lower(m_sasl->mechanism()) <<
"]" ) ;
343 m_session.auth_mechanism = m_sasl->mechanism() ;
345 std::string rsp_data = rsp.data.empty() ? std::string() :
std::string(1U,
' ').append(
G::Base64::encode(rsp.data)) ;
346 send(
"AUTH "_sv , m_session.auth_mechanism , rsp_data ,
"\r\n"_sv , rsp.sensitive ) ;
348 else if( m_protocol.state == State::Auth && !reply.
positive() && !m_config.authentication_fallthrough )
351 throw AuthError( *m_sasl , reply ) ;
353 else if( m_protocol.state == State::Auth && !reply.
positive() )
356 G_ASSERT( !m_session.authenticated ) ;
357 G_WARNING(
"GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl,reply).str() <<
": continuing" ) ;
358 m_protocol.state = State::Filtering ;
361 else if( m_protocol.state == State::Filtering && reply.
is(ClientReply::Value::Internal_filter_abandon) )
364 m_protocol.state = State::MessageDone ;
365 raiseDoneSignal( reply.
doneCode() , std::string() ) ;
367 else if( m_protocol.state == State::Filtering && reply.
is(ClientReply::Value::Internal_filter_error) )
370 m_protocol.state = State::MessageDone ;
373 else if( m_protocol.state == State::Filtering && reply.
is(ClientReply::Value::Internal_filter_ok) )
376 std::string_view reason = checkSendable() ;
377 if( !reason.empty() )
379 m_protocol.state = State::MessageDone ;
380 raiseDoneSignal( 0 ,
"failed" , G::sv_to_string(reason) ) ;
384 m_protocol.state = State::SentMail ;
388 else if( m_protocol.state == State::SentMail && reply.
is(ClientReply::Value::Ok_250) )
391 m_protocol.state = State::SentRcpt ;
394 else if( m_protocol.state == State::SentMail && !reply.
positive() )
397 m_protocol.state = State::MessageDone ;
400 else if( m_protocol.state == State::SentRcpt && m_message_state.to_index < message().toCount() )
405 m_message_state.to_accepted++ ;
407 m_message_state.to_rejected.push_back( message().to(m_message_state.to_index-1U) ) ;
410 else if( m_protocol.state == State::SentRcpt )
416 m_message_state.to_accepted++ ;
418 m_message_state.to_rejected.push_back( message().to(m_message_state.to_index-1U) ) ;
420 if( ( m_config.must_accept_all_recipients && m_message_state.to_accepted < message().toCount() ) || m_message_state.to_accepted == 0U )
422 m_protocol.state = State::SentDataStub ;
423 send(
"RSET\r\n"_sv ) ;
425 else if( ( message().bodyType() == BodyType::BinaryMime ||
G::Test::enabled(
"smtp-client-prefer-bdat") ) &&
426 m_session.server.has_binarymime && m_session.server.has_chunking )
429 m_message_state.content_size = message().contentSize() ;
430 std::string content_size_str = std::to_string( m_message_state.content_size ) ;
432 bool one_chunk = (m_message_state.content_size+5U) <= m_config.bdat_chunk_size ;
435 m_protocol.state = State::SentBdatLast ;
436 sendBdatAndChunk( m_message_state.content_size , content_size_str ,
true ) ;
440 m_protocol.state = State::SentBdatMore ;
442 m_message_state.chunk_data_size = m_config.bdat_chunk_size ;
443 m_message_state.chunk_data_size_str = std::to_string(m_message_state.chunk_data_size) ;
445 bool last = sendBdatAndChunk( m_message_state.chunk_data_size , m_message_state.chunk_data_size_str ,
false ) ;
447 m_protocol.state = State::SentBdatLast ;
452 m_protocol.state = State::SentData ;
453 send(
"DATA\r\n"_sv ) ;
456 else if( m_protocol.state == State::SentData && reply.
is(ClientReply::Value::OkForData_354) )
459 m_protocol.state = State::Data ;
460 std::size_t n = sendContentLines() ;
461 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
464 m_protocol.state = State::SentDot ;
468 else if( m_protocol.state == State::SentDataStub )
471 m_protocol.state = State::MessageDone ;
472 std::string how_many = m_config.must_accept_all_recipients ? std::string(
"one or more") :
std::string(
"all") ;
473 raiseDoneSignal( reply.
doneCode() , how_many +
" recipients rejected" ) ;
475 else if( m_protocol.state == State::SentBdatMore )
480 bool last = sendBdatAndChunk( m_message_state.chunk_data_size , m_message_state.chunk_data_size_str ,
false ) ;
482 m_protocol.state = State::SentBdatLast ;
489 else if( m_protocol.state == State::SentDot || m_protocol.state == State::SentBdatLast )
492 m_protocol.state = State::MessageDone ;
493 m_message_line.clear() ;
494 m_message_buffer.clear() ;
495 if( reply.
positive() && m_message_state.to_accepted < message().toCount() )
496 raiseDoneSignal( 0 ,
"one or more recipients rejected" ) ;
500 else if( m_protocol.state == State::Quitting && reply.
value() == 221 )
503 protocol_done = true ;
505 else if( is_start_event )
512 G_WARNING(
"GSmtp::ClientProtocol: client protocol: "
514 throw SmtpError(
"unexpected response" , reply.
errorText() ) ;
516 return protocol_done ;
524 G_ASSERT( !m_message_state.ptr.expired() ) ;
525 G_ASSERT( m_message_p !=
nullptr ) ;
526 if( m_message_state.ptr.expired() || m_message_p ==
nullptr )
527 throw SmtpError(
"invalid internal state" ) ;
529 return *m_message_p ;
537void GSmtp::ClientProtocol::onTimeout()
539 if( m_protocol.state == State::Started )
542 G_WARNING(
"GSmtp::ClientProtocol: timeout: no greeting from remote server after "
543 << m_config.ready_timeout <<
"s: continuing" ) ;
544 m_protocol.state = State::SentEhlo ;
547 else if( m_protocol.state == State::Filtering )
549 throw SmtpError(
"filtering timeout" ) ;
551 else if( m_protocol.state == State::Data )
553 throw SmtpError(
"flow-control timeout after " +
G::Str::fromUInt(m_config.response_timeout) +
"s" ) ;
557 throw SmtpError(
"response timeout after " +
G::Str::fromUInt(m_config.response_timeout) +
"s" ) ;
561void GSmtp::ClientProtocol::startFiltering()
563 G_ASSERT( m_protocol.state == State::Filtering ) ;
564 m_filter_signal.emit() ;
569 if( result == Filter::Result::ok )
574 else if( result == Filter::Result::abandon )
586void GSmtp::ClientProtocol::raiseDoneSignal(
int response_code ,
const std::string & response ,
587 const std::string & reason )
589 if( !response.empty() && response_code == 0 )
590 G_WARNING(
"GSmtp::ClientProtocol: smtp client protocol: " << response << std::string_view(
": ",reason.empty()?0U:2U) <<
G::Str::printable(reason) ) ;
592 m_message_p = nullptr ;
595 m_done_signal.emit( { response_code , response , reason ,
G::StringArray(m_message_state.to_rejected) } ) ;
598bool GSmtp::ClientProtocol::endOfContent()
600 return !message().contentStream().good() ;
603std::string_view GSmtp::ClientProtocol::checkSendable()
605 const bool eightbitmime_mismatch =
606 message().bodyType() == BodyType::EightBitMime &&
607 !m_session.server.has_8bitmime ;
609 const bool utf8_mismatch =
610 message().utf8Mailboxes() &&
611 !m_session.server.has_smtputf8 ;
613 const bool binarymime_mismatch =
614 message().bodyType() == BodyType::BinaryMime &&
615 !( m_session.server.has_binarymime && m_session.server.has_chunking ) ;
617 if( eightbitmime_mismatch && m_config.eightbit_strict )
620 return "cannot send 8-bit message to 7-bit server" ;
622 else if( binarymime_mismatch && m_config.binarymime_strict )
625 return "cannot send binarymime message to a non-chunking server" ;
627 else if( utf8_mismatch && m_config.smtputf8_strict )
630 return "cannot send utf8 message to non-smtputf8 server" ;
635 if( eightbitmime_mismatch && !m_eightbit_warned )
637 m_eightbit_warned = true ;
638 G_WARNING(
"GSmtp::ClientProtocol::checkSendable: sending an eight-bit message "
639 "to a server that has not advertised the 8BITMIME extension" ) ;
641 if( binarymime_mismatch && !m_binarymime_warned )
643 m_binarymime_warned = true ;
644 G_WARNING(
"GSmtp::ClientProtocol::checkSendable: sending a binarymime message "
645 "to a server that has not advertised the BINARYMIME/CHUNKING extension" ) ;
647 if( utf8_mismatch && !m_utf8_warned )
649 m_utf8_warned = true ;
650 G_WARNING(
"GSmtp::ClientProtocol::checkSendable: sending a message with utf8 mailbox names"
651 " to a server that has not advertised the SMTPUTF8 extension" ) ;
657bool GSmtp::ClientProtocol::sendMailFrom()
659 bool use_bdat = false ;
660 std::string mail_from_tail = message().from() ;
661 mail_from_tail.append( 1U ,
'>' ) ;
663 if( message().bodyType() == BodyType::SevenBit )
665 if( m_session.server.has_8bitmime )
666 mail_from_tail.append(
" BODY=7BIT" ) ;
668 else if( message().bodyType() == BodyType::EightBitMime )
670 if( m_session.server.has_8bitmime )
671 mail_from_tail.append(
" BODY=8BITMIME" ) ;
673 else if( message().bodyType() == BodyType::BinaryMime )
675 if( m_session.server.has_binarymime && m_session.server.has_chunking )
677 mail_from_tail.append(
" BODY=BINARYMIME" ) ;
682 if( m_session.server.has_smtputf8 && message().utf8Mailboxes() )
684 mail_from_tail.append(
" SMTPUTF8" ) ;
687 if( m_session.authenticated )
689 if( m_config.anonymous )
691 mail_from_tail.append(
" AUTH=<>" ) ;
693 else if( message().fromAuthOut().empty() && !m_sasl->id().empty() )
697 mail_from_tail.append(
" AUTH=" ) ;
700 else if( m_session.authenticated &&
G::Xtext::valid(message().fromAuthOut()) )
702 mail_from_tail.append(
" AUTH=" ) ;
703 mail_from_tail.append( message().fromAuthOut() ) ;
707 mail_from_tail.append(
" AUTH=<>" ) ;
711 if( m_config.pipelining && m_session.server.has_pipelining )
720 std::string commands ;
721 commands.reserve( 2000U ) ;
722 commands.append(
"MAIL FROM:<").append(mail_from_tail).append(
"\r\n",2U) ;
723 const std::size_t n = message().toCount() ;
724 for( std::size_t i = 0U ; i < n ; i++ )
725 commands.append(
"RCPT TO:<").append(message().to(i)).append(
">\r\n",3U) ;
726 m_message_state.to_index = 0 ;
727 sendCommandLines( commands ) ;
731 send(
"MAIL FROM:<"_sv , mail_from_tail ,
"\r\n"_sv ) ;
736void GSmtp::ClientProtocol::sendRcptTo()
738 if( m_config.pipelining && m_session.server.has_pipelining )
740 m_message_state.to_index++ ;
744 G_ASSERT( m_message_state.to_index < message().toCount() ) ;
745 std::string to = message().to( m_message_state.to_index++ ) ;
746 send(
"RCPT TO:<"_sv , to ,
">\r\n"_sv ) ;
750std::size_t GSmtp::ClientProtocol::sendContentLines()
754 m_message_line.resize( 1U ) ;
755 m_message_line.at(0) =
'.' ;
757 std::size_t line_count = 0U ;
758 while( sendNextContentLine(m_message_line) )
764bool GSmtp::ClientProtocol::sendNextContentLine( std::string & line )
774 G_ASSERT( !line.empty() && line.at(0) ==
'.' ) ;
778 m_config.crlf_only ? G::Str::Eol::CrLf : G::Str::Eol::Cr_Lf_CrLf ,
781 line.append(
"\r\n" , 2U ) ;
782 ok = sendContentLineImp( line , line.at(1U) ==
'.' ? 0U : 1U ) ;
787void GSmtp::ClientProtocol::sendEhlo()
789 send(
"EHLO "_sv , m_config.ehlo ,
"\r\n"_sv ) ;
792void GSmtp::ClientProtocol::sendHelo()
794 send(
"HELO "_sv , m_config.ehlo ,
"\r\n"_sv ) ;
797void GSmtp::ClientProtocol::sendEot()
799 sendImp(
".\r\n"_sv ) ;
805 sendImp( s , rsp.sensitive ? 0U : std::string::npos ) ;
808void GSmtp::ClientProtocol::sendCommandLines(
const std::string & lines )
810 sendImp( {lines.data(),lines.size()} ) ;
813void GSmtp::ClientProtocol::send( std::string_view s )
818void GSmtp::ClientProtocol::send( std::string_view s0 , std::string_view s1 , std::string_view s2 , std::string_view s3 ,
bool s2_sensitive )
820 std::string line = std::string(s0.data(),s0.size()).append(s1.data(),s1.size()).append(s2.data(),s2.size()).append(s3.data(),s3.size()) ;
821 sendImp( line , ( s2_sensitive && !s2.empty() ) ? (s0.size()+s1.size()) : std::string::npos ) ;
824bool GSmtp::ClientProtocol::sendBdatAndChunk( std::size_t size ,
const std::string & size_str ,
bool last )
834 std::size_t buffer_size = size + (last?12U:7U) + size_str.size() ;
835 std::size_t eolpos = (last?10U:5U) + size_str.size() ;
836 std::size_t datapos = eolpos + 2U ;
837 std::size_t margin = last ? 0U : 10U ;
839 m_message_buffer.resize( buffer_size + margin ) ;
840 char * out = m_message_buffer.data() + margin ;
842 std::memcpy( out ,
"BDAT " , 5U ) ;
843 std::memcpy( out+5U , size_str.data() , size_str.size() ) ;
845 std::memcpy( out+5U+size_str.size() ,
" LAST" , 5U ) ;
846 std::memcpy( out+eolpos ,
"\r\n" , 2U ) ;
848 G_ASSERT( buffer_size > datapos ) ;
849 G_ASSERT( (out+datapos) < (m_message_buffer.data()+m_message_buffer.size()) ) ;
850 message().contentStream().read( out+datapos , buffer_size-datapos ) ;
851 std::streamsize gcount = message().contentStream().gcount() ;
853 G_ASSERT( gcount >= 0 ) ;
855 std::size_t nread =
static_cast<std::size_t
>( gcount ) ;
857 bool eof = (datapos+nread) < buffer_size ;
863 std::string n = std::to_string( nread ) ;
864 std::size_t cmdsize = 12U + n.size() ;
865 out = out + datapos - cmdsize ;
867 G_ASSERT( n.size() <= size_str.size() ) ;
868 G_ASSERT( out >= m_message_buffer.data() ) ;
869 std::memcpy( out ,
"BDAT " , 5U ) ;
870 std::memcpy( out+5U , n.data() , n.size() ) ;
871 std::memcpy( out+5U+n.size(),
" LAST\r\n" , 7U ) ;
874 sendChunkImp( out , datapos+nread ) ;
880void GSmtp::ClientProtocol::sendChunkImp(
const char * p , std::size_t n )
882 std::string_view sv( p , n ) ;
884 if( m_config.response_timeout != 0U )
885 startTimer( m_config.response_timeout ) ;
887 if( G::LogOutput::Instance::atVerbose() )
889 std::size_t pos = sv.find(
"\r\n"_sv ) ;
892 std::string_view count = t.next()() ;
893 std::string_view end = count.size() == 1U && count[0] ==
'1' ?
"]"_sv :
"s]"_sv ;
894 G_LOG(
"GSmtp::ClientProtocol: tx>>: \"" << cmd <<
"\" [" << count <<
" byte" << end ) ;
897 m_sender.protocolSend( sv , 0U ,
false ) ;
900bool GSmtp::ClientProtocol::sendContentLineImp(
const std::string & line , std::size_t offset )
902 bool all_sent = m_sender.protocolSend( line , offset ,
false ) ;
903 if( !all_sent && m_config.response_timeout != 0U )
904 startTimer( m_config.response_timeout ) ;
908bool GSmtp::ClientProtocol::sendImp( std::string_view line , std::size_t sensitive_from )
910 G_ASSERT( line.size() > 2U && line.rfind(
'\n') == (line.size()-1U) ) ;
912 if( m_protocol.state == State::Quitting )
914 else if( m_config.response_timeout != 0U )
915 startTimer( m_config.response_timeout ) ;
917 std::size_t pos = 0U ;
920 if( sensitive_from == std::string::npos || (pos+f.size()) < sensitive_from )
921 G_LOG(
"GSmtp::ClientProtocol: tx>>: "
923 else if( pos >= sensitive_from )
924 G_LOG(
"GSmtp::ClientProtocol: tx>>: [response not logged]" ) ;
926 G_LOG(
"GSmtp::ClientProtocol: tx>>: "
927 "\"" <<
G::Str::printable(f().substr(0U,sensitive_from-pos)) <<
" [not logged]\"" ) ;
930 return m_sender.protocolSend( line , 0U ,
false ) ;
935GSmtp::ClientProtocolImp::EhloReply::EhloReply(
const ClientReply & reply ) :
938 G_ASSERT( reply.is(ClientReply::Value::Ok_250) ) ;
941bool GSmtp::ClientProtocolImp::EhloReply::has(
const std::string & option )
const
943 return m_reply.text().find(std::string(1U,
'\n').append(option)) != std::string::npos ;
946G::StringArray GSmtp::ClientProtocolImp::EhloReply::values(
const std::string & option )
const
949 std::string text = m_reply.text() ;
950 std::size_t start_pos = text.find( std::string(1U,
'\n').append(option).append(1U,
' ') ) ;
951 if( start_pos != std::string::npos )
953 std::size_t end_pos = text.find(
'\n' , start_pos+1U ) ;
954 std::size_t size = end_pos == std::string::npos ? end_pos : ( end_pos - start_pos ) ;
956 G_ASSERT( result.at(0U) == option ) ;
957 if( !result.empty() ) result.erase( result.begin() ) ;
964std::size_t GSmtp::ClientProtocol::Protocol::replySize()
const
966 return std::accumulate( reply_lines.begin() , reply_lines.end() , std::size_t(0U) ,
967 [](std::size_t n,
const std::string& s){return n+s.size();} ) ;
972GSmtp::ClientProtocol::Config::Config()
978 const ClientReply & reply ) :
979 SmtpError(
"authentication failed " + sasl.info() +
": [" +
G::Str::printable(reply.text()) +
"]" )
983std::string GSmtp::ClientProtocolImp::AuthError::str()
const
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(std::string_view selector, std::size_t limit=0U) const
Returns an optional initial response.
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
An interface used by ClientProtocol to send protocol messages.
ClientProtocol(GNet::EventState, Sender &sender, const GAuth::SaslClientSecrets &secrets, const std::string &sasl_client_config, const Config &config, bool in_secure_tunnel)
Constructor.
void start(std::weak_ptr< GStore::StoredMessage >)
Starts transmission of the given message.
void reconfigure(const std::string &ehlo)
Updates a configuration parameter after construction.
void secure()
To be called when the secure socket protocol has been successfully established.
void finish()
Called after the last message has been sent.
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 encode(std::string_view, std::string_view line_break={})
Encodes the given string, optionally inserting line-breaks to limit the line length.
static std::string decode(std::string_view, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
static bool valid(std::string_view, bool strict=true)
Returns true if the string is a valid base64 encoding, possibly allowing for embedded newlines,...
static std::string lower(std::string_view)
Returns a copy of 's' in which all seven-bit upper-case characters have been replaced by lower-case c...
static void splitIntoTokens(const std::string &in, StringArray &out, std::string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
static std::istream & readLine(std::istream &stream, std::string &result, std::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 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 std::string_view ws() noexcept
Returns a string of standard whitespace characters.
static std::string_view headView(std::string_view in, std::size_t pos, std::string_view default_={}) noexcept
Like head() but returning a view into the input string.
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(std::string_view)
Encodes the given string.
static bool valid(std::string_view, bool strict=false)
Returns true if a valid encoding, or empty.
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.