37 const Config & config ,
bool in_secure_tunnel ) :
41 m_sasl(std::make_unique<
GAuth::SaslClient>(m_secrets,sasl_client_config)) ,
43 m_state(State::sInit) ,
46 m_server_has_starttls(false) ,
47 m_server_has_auth(false) ,
48 m_server_secure(false) ,
49 m_server_has_8bitmime(false) ,
50 m_authenticated_with_server(false) ,
51 m_in_secure_tunnel(in_secure_tunnel) ,
59 G_DEBUG(
"GSmtp::ClientProtocol::start" ) ;
62 m_message = message_in ;
65 m_to_rejected.clear() ;
69 m_done_signal.reset() ;
70 applyEvent(
Reply() ,
true ) ;
73std::shared_ptr<GSmtp::StoredMessage> GSmtp::ClientProtocol::message()
75 G_ASSERT( !m_message.expired() ) ;
76 if( m_message.expired() )
77 return std::make_shared<StoredMessageStub>() ;
78 return m_message.lock() ;
83 G_DEBUG(
"GSmtp::ClientProtocol::finish" ) ;
84 m_config.response_timeout = 1U ;
85 m_state = State::sQuitting ;
92 applyEvent( Reply::ok(Reply::Value::Internal_secure) ) ;
97 if( m_state == State::sData )
99 std::size_t n = sendLines() ;
101 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
104 m_state = State::sSentDot ;
110bool GSmtp::ClientProtocol::parseReply( Reply & stored_reply ,
const std::string & rx , std::string & reason )
112 Reply this_reply = Reply( rx ) ;
113 if( ! this_reply.validFormat() )
115 stored_reply = Reply() ;
116 reason =
"invalid reply format" ;
119 else if( stored_reply.validFormat() && stored_reply.incomplete() )
121 if( ! stored_reply.add(this_reply) )
123 stored_reply = Reply() ;
124 reason =
"invalid continuation line" ;
130 stored_reply = this_reply ;
132 return ! stored_reply.incomplete() ;
140 bool protocol_done = false ;
141 bool complete_reply = parseReply( m_reply , rx , reason ) ;
144 protocol_done = applyEvent( m_reply ) ;
148 if( reason.length() != 0U )
149 send(
"550 syntax error: " , reason ) ;
151 return protocol_done ;
154void GSmtp::ClientProtocol::sendEhlo()
156 send(
"EHLO " , m_config.thishost_name ) ;
159void GSmtp::ClientProtocol::sendHelo()
161 send(
"HELO " , m_config.thishost_name ) ;
164void GSmtp::ClientProtocol::sendMail()
166 const bool mismatch = message()->eightBit() == 1 && !m_server_has_8bitmime ;
167 if( mismatch && m_config.eight_bit_strict )
170 m_state = State::sMessageDone ;
171 raiseDoneSignal( 0 ,
"failed" ,
"cannot send 8-bit message to 7-bit server" ) ;
175 if( mismatch && !m_warned )
178 G_WARNING(
"GSmtp::ClientProtocol::sendMail: sending an eight-bit message "
179 "to a server which has not advertised the 8BITMIME extension" ) ;
186void GSmtp::ClientProtocol::sendMailCore()
188 std::string mail_from_tail = message()->from() ;
189 mail_from_tail.append( 1U ,
'>' ) ;
190 if( m_server_has_8bitmime && message()->eightBit() != -1 )
192 mail_from_tail.append( message()->eightBit() ?
" BODY=8BITMIME" :
" BODY=7BIT" ) ;
194 if( m_authenticated_with_server && message()->fromAuthOut().empty() && !m_sasl->id().empty() )
198 mail_from_tail.append(
" AUTH=" ) ;
201 else if( m_authenticated_with_server &&
G::Xtext::valid(message()->fromAuthOut()) )
203 mail_from_tail.append(
" AUTH=" ) ;
204 mail_from_tail.append( message()->fromAuthOut() ) ;
206 else if( m_authenticated_with_server )
208 mail_from_tail.append(
" AUTH=<>" ) ;
210 send(
"MAIL FROM:<" , mail_from_tail ) ;
213bool GSmtp::ClientProtocol::applyEvent(
const Reply & reply ,
bool is_start_event )
215 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: " << reply.value() <<
": " <<
G::Str::printable(reply.text()) ) ;
219 bool protocol_done = false ;
220 if( m_state == State::sInit && is_start_event )
223 m_state = State::sStarted ;
224 if( m_config.ready_timeout != 0U )
225 startTimer( m_config.ready_timeout ) ;
227 else if( m_state == State::sInit && reply.is(Reply::Value::ServiceReady_220) )
230 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: init -> ready" ) ;
231 m_state = State::sServiceReady ;
233 else if( m_state == State::sServiceReady && is_start_event )
236 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: ready -> sent-ehlo" ) ;
237 m_state = State::sSentEhlo ;
240 else if( m_state == State::sStarted && reply.is(Reply::Value::ServiceReady_220) )
243 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: start -> sent-ehlo" ) ;
244 m_state = State::sSentEhlo ;
247 else if( m_state == State::sMessageDone && is_start_event )
250 m_state = State::sFiltering ;
253 else if( m_state == State::sSentEhlo && (
254 reply.is(Reply::Value::SyntaxError_500) ||
255 reply.is(Reply::Value::SyntaxError_501) ||
256 reply.is(Reply::Value::NotImplemented_502) ) )
259 if( m_config.must_use_tls && !m_in_secure_tunnel )
260 throw SmtpError(
"tls is mandated but the server cannot do esmtp" ) ;
261 m_state = State::sSentHelo ;
264 else if( ( m_state == State::sSentEhlo || m_state == State::sSentHelo || m_state == State::sSentTlsEhlo ) &&
265 reply.is(Reply::Value::Ok_250) )
268 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: ehlo/rset reply \"" <<
G::Str::printable(reply.text()) <<
"\"" ) ;
269 if( m_state == State::sSentEhlo || m_state == State::sSentTlsEhlo )
271 m_server_has_starttls = m_state == State::sSentEhlo && reply.textContains(
"\nSTARTTLS") ;
272 m_server_has_8bitmime = reply.textContains(
"\n8BITMIME");
273 m_server_has_auth = serverAuth( reply ) ;
274 m_server_auth_mechanisms = serverAuthMechanisms( reply ) ;
275 m_server_secure = m_state == State::sSentTlsEhlo || m_in_secure_tunnel ;
277 m_auth_mechanism = m_sasl->mechanism( m_server_auth_mechanisms ) ;
279 if( !m_server_secure && m_config.must_use_tls )
281 if( !m_server_has_starttls )
282 throw SmtpError(
"tls is mandated but the server cannot do starttls" ) ;
283 m_state = State::sStartTls ;
286 else if( !m_server_secure && m_config.use_starttls_if_possible && m_server_has_starttls )
288 m_state = State::sStartTls ;
291 else if( m_server_has_auth && !m_sasl->active() )
294 G_LOG(
"GSmtp::ClientProtocol: not authenticating with the remote server since no "
295 "client authentication secret has been configured" ) ;
296 m_state = State::sFiltering ;
299 else if( m_server_has_auth && m_sasl->active() && m_auth_mechanism.empty() )
301 throw SmtpError(
"cannot do authentication required by remote server "
303 "check for a compatible client secret" ) ;
305 else if( m_server_has_auth && m_sasl->active() )
307 m_state = State::sAuth ;
308 send(
"AUTH " , m_auth_mechanism , initialResponse(*m_sasl) ) ;
310 else if( !m_server_has_auth && m_sasl->active() && m_config.must_authenticate )
312 throw SmtpError(
"authentication is not supported by the remote smtp server" ) ;
316 m_state = State::sFiltering ;
320 else if( m_state == State::sStartTls && reply.is(Reply::Value::ServiceReady_220) )
323 m_sender.protocolSend( std::string() , 0U ,
true ) ;
325 else if( m_state == State::sStartTls && reply.is(Reply::Value::NotAvailable_454) )
328 throw TlsError( reply.errorText() ) ;
330 else if( m_state == State::sStartTls && reply.is(Reply::Value::Internal_secure) )
333 m_state = State::sSentTlsEhlo ;
336 else if( m_state == State::sAuth && reply.is(Reply::Value::Challenge_334) &&
337 ( reply.text() ==
"=" ||
G::Base64::valid(reply.text()) || m_auth_mechanism ==
"PLAIN" ) )
347 else if( m_state == State::sAuth && reply.is(Reply::Value::Challenge_334) )
352 else if( m_state == State::sAuth && reply.positive() )
355 m_authenticated_with_server = true ;
356 G_LOG(
"GSmtp::ClientProtocol::applyEvent: successful authentication with remote server "
357 << (m_server_secure?
"over tls ":
"") << m_sasl->info() ) ;
358 m_state = State::sFiltering ;
361 else if( m_state == State::sAuth && !reply.positive() && m_sasl->next() )
364 G_LOG(
"GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl,reply).str()
365 <<
": trying [" <<
G::Str::lower(m_sasl->mechanism()) <<
"]" ) ;
366 m_auth_mechanism = m_sasl->mechanism() ;
367 send(
"AUTH " , m_auth_mechanism , initialResponse(*m_sasl) ) ;
369 else if( m_state == State::sAuth && !reply.positive() && m_config.must_authenticate )
372 throw AuthError( *m_sasl , reply ) ;
374 else if( m_state == State::sAuth && !reply.positive() )
377 G_ASSERT( !m_authenticated_with_server ) ;
378 G_WARNING(
"GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl,reply).str() <<
": continuing" ) ;
379 m_state = State::sFiltering ;
382 else if( m_state == State::sFiltering && reply.is(Reply::Value::Internal_filter_ok) )
385 m_state = State::sSentMail ;
388 else if( m_state == State::sFiltering && reply.is(Reply::Value::Internal_filter_abandon) )
391 m_state = State::sMessageDone ;
392 raiseDoneSignal( -1 , std::string() ) ;
394 else if( m_state == State::sFiltering && reply.is(Reply::Value::Internal_filter_error) )
397 m_state = State::sMessageDone ;
398 raiseDoneSignal( -2 , reply.errorText() , reply.errorReason() ) ;
400 else if( m_state == State::sSentMail && reply.is(Reply::Value::Ok_250) )
403 G_ASSERT( m_to_index == 0U && message()->toCount() != 0U ) ;
404 std::string to = message()->to( m_to_index++ ) ;
405 G_ASSERT( !to.empty() ) ;
406 m_state = State::sSentRcpt ;
407 send(
"RCPT TO:<" , to ,
">" ) ;
409 else if( m_state == State::sSentRcpt && m_to_index < message()->toCount() )
412 if( reply.positive() )
415 m_to_rejected.push_back( message()->to(m_to_index-1U) ) ;
417 std::string to = message()->to( m_to_index++ ) ;
418 send(
"RCPT TO:<" , to ,
">" ) ;
420 else if( m_state == State::sSentRcpt )
423 if( reply.positive() )
426 m_to_rejected.push_back( message()->to(m_to_index-1U) ) ;
428 if( ( m_config.must_accept_all_recipients && m_to_accepted < message()->toCount() ) || m_to_accepted == 0U )
430 m_state = State::sSentDataStub ;
435 m_state = State::sSentData ;
439 else if( m_state == State::sSentData && reply.is(Reply::Value::OkForData_354) )
442 m_state = State::sData ;
443 std::size_t n = sendLines() ;
444 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
447 m_state = State::sSentDot ;
451 else if( m_state == State::sSentDataStub )
454 m_state = State::sMessageDone ;
455 std::string how_many = m_config.must_accept_all_recipients ? std::string(
"one or more") : std::string(
"all") ;
456 raiseDoneSignal( reply.value() , how_many +
" recipients rejected" ) ;
458 else if( m_state == State::sSentDot )
461 m_state = State::sMessageDone ;
462 if( m_to_accepted < message()->toCount() && reply.positive() )
463 raiseDoneSignal( 0 ,
"one or more recipients rejected" ) ;
465 raiseDoneSignal( reply.value() , reply.errorText() ) ;
467 else if( m_state == State::sQuitting && reply.value() == 221 )
470 protocol_done = true ;
472 else if( is_start_event )
479 G_WARNING(
"GSmtp::ClientProtocol: client protocol: "
481 throw SmtpError(
"unexpected response" , reply.errorText() ) ;
483 return protocol_done ;
486std::string GSmtp::ClientProtocol::initialResponse(
const GAuth::SaslClient & sasl )
492void GSmtp::ClientProtocol::onTimeout()
494 if( m_state == State::sStarted )
497 G_WARNING(
"GSmtp::ClientProtocol: timeout: no greeting from remote server after "
498 << m_config.ready_timeout <<
"s: continuing" ) ;
499 m_state = State::sSentEhlo ;
502 else if( m_state == State::sFiltering )
504 throw SmtpError(
"filtering timeout after " +
G::Str::fromUInt(m_config.filter_timeout) +
"s" ) ;
506 else if( m_state == State::sData )
508 throw SmtpError(
"flow-control timeout after " +
G::Str::fromUInt(m_config.response_timeout) +
"s" ) ;
512 throw SmtpError(
"response timeout after " +
G::Str::fromUInt(m_config.response_timeout) +
"s" ) ;
516bool GSmtp::ClientProtocol::serverAuth(
const ClientProtocolReply & reply )
const
518 return !reply.textLine(
"AUTH ").empty() ;
521G::StringArray GSmtp::ClientProtocol::serverAuthMechanisms(
const ClientProtocolReply & reply )
const
524 std::string auth_line = reply.textLine(
"AUTH ") ;
525 if( ! auth_line.empty() )
527 std::string tail =
G::Str::tail( auth_line , auth_line.find(
' ') , std::string() ) ;
533void GSmtp::ClientProtocol::startFiltering()
535 G_ASSERT( m_state == State::sFiltering ) ;
536 if( m_config.filter_timeout != 0U )
537 startTimer( m_config.filter_timeout ) ;
538 m_filter_signal.emit() ;
546 applyEvent( Reply::ok(Reply::Value::Internal_filter_ok) ) ;
548 else if( response.empty() )
551 applyEvent( Reply::ok(Reply::Value::Internal_filter_abandon) ) ;
556 applyEvent( Reply::error(Reply::Value::Internal_filter_error,response,reason) ) ;
560void GSmtp::ClientProtocol::raiseDoneSignal(
int response_code ,
const std::string & response ,
561 const std::string & reason )
563 if( !response.empty() && response_code == 0 )
564 G_WARNING(
"GSmtp::ClientProtocol: smtp client protocol: " << response ) ;
567 m_done_signal.emit( response_code , std::string(response) , std::string(reason) ,
G::StringArray(m_to_rejected) ) ;
570bool GSmtp::ClientProtocol::endOfContent()
572 return !message()->contentStream().good() ;
575std::size_t GSmtp::ClientProtocol::sendLines()
580 std::string read_buffer( 200U ,
'.' ) ;
583 while( sendLine(read_buffer) )
588bool GSmtp::ClientProtocol::sendLine( std::string & line )
593 if( message()->contentStream().good() )
595 std::istream & stream = message()->contentStream() ;
596 const bool pre_erase = false ;
598 G_ASSERT( line.length() >= 1U && line.at(0U) ==
'.' ) ;
603 if( !line.empty() && line.at(line.size()-1U) !=
'\r' )
604 line.append( 1U ,
'\r' ) ;
605 line.append( 1U ,
'\n' ) ;
607 bool all_sent = m_sender.protocolSend( line , line.at(1U) ==
'.' ? 0U : 1U ,
false ) ;
608 if( !all_sent && m_config.response_timeout != 0U )
609 startTimer( m_config.response_timeout ) ;
616void GSmtp::ClientProtocol::send(
const char * p )
618 send( std::string(p) ,
false ,
false ) ;
621void GSmtp::ClientProtocol::send(
const char * p ,
const std::string & s ,
const std::string & p2 )
623 std::string line( p ) ;
626 send( line ,
false ,
false ) ;
629void GSmtp::ClientProtocol::send(
const char * p ,
const std::string & s )
631 send( std::string(p) + s ,
false ,
false ) ;
634bool GSmtp::ClientProtocol::send(
const std::string & line ,
bool eot ,
bool sensitive )
636 if( m_config.response_timeout != 0U )
637 startTimer( m_config.response_timeout ) ;
639 bool dot_prefix = !eot && line.length() && line.at(0U) ==
'.' ;
642 G_LOG(
"GSmtp::ClientProtocol: tx>>: [response not logged]" ) ;
646 G_LOG(
"GSmtp::ClientProtocol: tx>>: \"" << (dot_prefix?
".":
"") <<
G::Str::printable(line) <<
"\"" ) ;
648 return m_sender.protocolSend( (dot_prefix?
".":
"") + line +
"\r\n" , 0U ,
false ) ;
653 return m_done_signal ;
658 return m_filter_signal ;
665 if( line.length() >= 3U &&
666 is_digit(line.at(0U)) &&
667 line.at(0U) <=
'5' &&
668 is_digit(line.at(1U)) &&
669 is_digit(line.at(2U)) &&
670 ( line.length() == 3U || line.at(3U) ==
' ' || line.at(3U) ==
'-' ) )
673 m_complete = line.length() == 3U || line.at(3U) ==
' ' ;
675 if( line.length() > 4U )
677 m_text = line.substr(4U) ;
692 reply.m_value =
static_cast<int>(v) ;
694 reply.m_text =
"OK\n" + text ;
695 G_ASSERT( reply.
positive() ) ;
if( !reply.
positive() ) reply.m_value = 250 ;
700 const std::string & reason )
703 int vv =
static_cast<int>(v) ;
704 reply.m_value = ( vv >= 500 && vv < 600 ) ? vv : 500 ;
705 reply.m_reason = reason ;
716 return ! m_complete ;
721 return m_valid && m_value < 400 ;
726 return m_valid ? m_value : 0 ;
731 return value() ==
static_cast<int>(v) ;
736 const bool positive_completion = type() == Type::PositiveCompletion ;
737 return positive_completion ? std::string() : ( m_text.empty() ? std::string(
"error") : m_text ) ;
752 std::size_t start_pos = m_text.find( std::string(
"\n")+prefix ) ;
753 if( start_pos == std::string::npos )
755 return std::string() ;
760 std::size_t end_pos = m_text.find(
'\n' , start_pos + prefix.length() ) ;
761 return m_text.substr( start_pos , end_pos-start_pos ) ;
765bool GSmtp::ClientProtocolReply::is_digit(
char c )
767 return c >=
'0' && c <=
'9' ;
772 G_ASSERT( m_valid && (m_value/100) >= 1 && (m_value/100) <= 5 ) ;
773 return static_cast<Type
>( m_value / 100 ) ;
778 G_ASSERT( m_valid && m_value >= 0 ) ;
779 int n = ( m_value / 10 ) % 10 ;
781 return static_cast<SubType
>( n ) ;
783 return SubType::Invalid_SubType ;
788 G_ASSERT( other.m_valid ) ;
789 G_ASSERT( m_valid ) ;
790 G_ASSERT( !m_complete ) ;
792 m_complete = other.m_complete ;
793 m_text.append( std::string(
"\n") + other.
text() ) ;
794 return value() == other.
value() ;
799 std::string text( m_text ) ;
802 return text.find(key) != std::string::npos ;
807GSmtp::ClientProtocol::Config::Config()
810GSmtp::ClientProtocol::Config::Config(
const std::string & name_ ,
811 unsigned int response_timeout_ ,
812 unsigned int ready_timeout_ ,
unsigned int filter_timeout_ ,
813 bool use_starttls_if_possible_ ,
bool must_use_tls_ ,
814 bool must_authenticate_ ,
bool anonymous_ ,
815 bool must_accept_all_recipients_ ,
bool eight_bit_strict_ ) :
816 thishost_name(name_) ,
817 response_timeout(response_timeout_) ,
818 ready_timeout(ready_timeout_) ,
819 filter_timeout(filter_timeout_) ,
820 use_starttls_if_possible(use_starttls_if_possible_) ,
821 must_use_tls(must_use_tls_) ,
822 must_authenticate(must_authenticate_) ,
823 anonymous(anonymous_) ,
824 must_accept_all_recipients(must_accept_all_recipients_) ,
825 eight_bit_strict(eight_bit_strict_)
833 SmtpError(
"authentication failed " + sasl.info() +
": [" +
G::Str::printable(reply.text()) +
"]" )
837std::string GSmtp::ClientProtocol::AuthError::str()
const
839 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.
std::string initialResponse(std::size_t limit=0U) const
Returns an optional initial response.
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
A private implementation class used by ClientProtocol.
std::string text() const
Returns the text of the reply, excluding the numeric part, and with embedded newlines.
bool is(Value v) const
Returns true if the reply value is 'v'.
std::string textLine(const std::string &prefix) const
Returns a line of text() which starts with prefix.
std::string errorReason() const
Returns an error reason string, as passed to error().
bool textContains(std::string s) const
Returns true if the text() contains the given substring.
SubType subType() const
Returns the reply sub-type.
bool add(const ClientProtocolReply &other)
Adds more lines to this reply.
Type type() const
Returns the reply type (category).
bool positive() const
Returns true if the numeric value of the reply is less than four hundred.
int value() const
Returns the numeric value of the reply.
std::string errorText() const
Returns the text() string, plus any error reason, but with the guarantee that the returned string is ...
static ClientProtocolReply error(Value, const std::string &response, const std::string &error_reason)
Factory function for an error reply with a specific 5xx value.
bool incomplete() const
Returns true if the reply is incomplete.
ClientProtocolReply(const std::string &line=std::string())
Constructor for one line of text.
static ClientProtocolReply ok()
Factory function for an ok reply.
bool validFormat() const
Returns true if a valid format.
An interface used by ClientProtocol to send protocol messages.
G::Slot::Signal & filterSignal()
Returns a signal that is raised when the protocol needs to do message filtering.
G::Slot::Signal< int, const std::string &, const std::string &, const G::StringArray & > & doneSignal()
Returns a signal that is raised once the protocol has finished with a 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.
void start(std::weak_ptr< StoredMessage >)
Starts transmission of the given message.
ClientProtocol(GNet::ExceptionSink, Sender &sender, const GAuth::SaslClientSecrets &secrets, const std::string &sasl_client_config, const Config &config, bool in_secure_tunnel)
Constructor.
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.
void filterDone(bool ok, const std::string &response, const std::string &reason)
To be called when the Filter interface has done its thing.
static std::string decode(const std::string &, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
static std::string encode(const std::string &s, const std::string &line_break=std::string())
Encodes the given string, optionally inserting line-breaks to limit the line length.
static bool valid(const std::string &, bool strict=true)
Returns true if the string is a valid base64 encoding, possibly allowing for embedded newlines,...
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
static string_view ws()
Returns a string of standard whitespace characters.
static std::string tail(const std::string &in, std::size_t pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
static std::string & trimLeft(std::string &s, string_view ws, std::size_t limit=0U)
Trims the lhs of s, taking off up to 'limit' of the 'ws' characters.
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 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 void toUpper(std::string &s)
Replaces all Latin-1 lower-case characters in string 's' by upper-case characters.
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
static int toInt(const std::string &s)
Converts string 's' to an int.
static std::string lower(const std::string &s)
Returns a copy of 's' in which all Latin-1 upper-case characters have been replaced by lower-case cha...
static std::string readLineFrom(std::istream &stream, const std::string &eol=std::string())
Reads a line from the stream using the given line terminator.
static bool valid(const std::string &, bool strict=false)
Returns true if a valid encoding.
static std::string encode(const std::string &)
Encodes the given string.
An interface to an underlying TLS library.
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.