40 m_thishost(config.thishost_name) ,
44 m_server_has_auth(false) ,
45 m_server_has_8bitmime(false) ,
46 m_server_has_tls(false) ,
47 m_message_is_8bit(false) ,
48 m_authenticated_with_server(false) ,
49 m_must_authenticate(config.must_authenticate) ,
50 m_must_accept_all_recipients(config.must_accept_all_recipients) ,
51 m_strict(config.eight_bit_strict) ,
53 m_response_timeout(config.response_timeout) ,
54 m_ready_timeout(config.ready_timeout) ,
55 m_preprocessor_timeout(config.preprocessor_timeout) ,
61 std::string authentication , std::string server_name , std::auto_ptr<std::istream> content )
63 G_DEBUG(
"GSmtp::ClientProtocol::start" ) ;
67 m_to_size = to.size() ;
71 m_message_is_8bit = eight_bit ;
72 m_message_authentication = authentication ;
75 m_done_signal.reset() ;
78 applyEvent(
Reply() ,
true ) ;
86 applyEvent( Reply::ok(Reply::Internal_2xx) ) ;
88 else if( reason.empty() )
91 applyEvent( Reply::ok(Reply::Internal_2zz) ) ;
95 std::string error =
"preprocessing: " + reason ;
96 applyEvent( Reply::error(error) ) ;
103 applyEvent( Reply::ok(Reply::Internal_2yy) ) ;
108 if( m_state == sData )
110 size_t n = sendLines() ;
112 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
121 bool GSmtp::ClientProtocol::parseReply( Reply & stored_reply ,
const std::string & rx , std::string & reason )
123 Reply this_reply = Reply( rx ) ;
124 if( ! this_reply.validFormat() )
126 stored_reply = Reply() ;
127 reason =
"invalid reply format" ;
130 else if( stored_reply.validFormat() && stored_reply.incomplete() )
132 if( ! stored_reply.add(this_reply) )
134 stored_reply = Reply() ;
135 reason =
"invalid continuation line" ;
141 stored_reply = this_reply ;
143 return ! stored_reply.incomplete() ;
151 bool protocol_done = false ;
152 bool complete_reply = parseReply( m_reply , rx , reason ) ;
155 protocol_done = applyEvent( m_reply ) ;
159 if( reason.length() != 0U )
160 send(
"550 syntax error: " , reason ) ;
162 return protocol_done ;
165 void GSmtp::ClientProtocol::sendEhlo()
167 send(
"EHLO " , m_thishost ) ;
170 void GSmtp::ClientProtocol::sendHelo()
172 send(
"HELO " , m_thishost ) ;
175 void GSmtp::ClientProtocol::sendMail()
177 const bool dodgy = m_message_is_8bit && !m_server_has_8bitmime ;
178 if( dodgy && m_strict )
181 raiseDoneSignal(
"cannot send 8-bit message to 7-bit server" , 0 ,
true ) ;
185 if( dodgy && !m_warned )
188 G_WARNING(
"GSmtp::ClientProtocol::sendMail: sending an eight-bit message "
189 "to a server which has not advertised the 8BITMIME extension" ) ;
195 void GSmtp::ClientProtocol::sendMailCore()
197 std::string mail_from_tail = m_from ;
198 mail_from_tail.append( 1U ,
'>' ) ;
199 if( m_server_has_8bitmime )
201 mail_from_tail.append(
" BODY=8BITMIME" ) ;
203 if( m_authenticated_with_server && !m_message_authentication.empty() )
205 mail_from_tail.append(
" AUTH=" ) ;
208 else if( m_authenticated_with_server )
210 mail_from_tail.append(
" AUTH=<>" ) ;
212 send(
"MAIL FROM:<" , mail_from_tail ) ;
215 void GSmtp::ClientProtocol::startPreprocessing()
217 G_ASSERT( m_state == sPreprocessing ) ;
218 if( m_preprocessor_timeout != 0U )
219 startTimer( m_preprocessor_timeout ) ;
220 m_preprocessor_signal.emit() ;
223 bool GSmtp::ClientProtocol::applyEvent(
const Reply & reply ,
bool is_start_event )
225 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: " << reply.value() <<
": " << reply.text() ) ;
229 bool protocol_done = false ;
230 if( m_state == sInit && is_start_event )
234 if( m_ready_timeout != 0U )
235 startTimer( m_ready_timeout ) ;
237 else if( m_state == sServiceReady && is_start_event )
240 m_state = sSentEhlo ;
243 else if( m_state == sDone && is_start_event )
245 m_state = sPreprocessing ;
246 startPreprocessing() ;
248 else if( m_state == sInit && reply.is(Reply::ServiceReady_220) )
250 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: init -> ready" ) ;
251 m_state = sServiceReady ;
253 else if( m_state == sStarted && reply.is(Reply::ServiceReady_220) )
255 G_DEBUG(
"GSmtp::ClientProtocol::applyEvent: start -> sent-ehlo" ) ;
256 m_state = sSentEhlo ;
259 else if( m_state == sSentEhlo && (
260 reply.is(Reply::SyntaxError_500) ||
261 reply.is(Reply::SyntaxError_501) ||
262 reply.is(Reply::NotImplemented_502) ) )
265 m_state = sSentHelo ;
268 else if( ( m_state == sSentEhlo || m_state == sSentHelo || m_state == sSentTlsEhlo ) && reply.is(Reply::Ok_250) )
273 m_server_has_auth = serverAuth( reply ) ;
274 m_server_has_8bitmime = ( m_state == sSentEhlo || m_state == sSentTlsEhlo ) && reply.textContains(
"\n8BITMIME");
275 m_server_has_tls = m_state == sSentTlsEhlo || ( m_state == sSentEhlo && reply.textContains(
"\nSTARTTLS") ) ;
276 m_auth_mechanism = m_sasl->preferred( serverAuthMechanisms(reply) ) ;
280 std::string msg(
"GSmtp::ClientProtocol::applyEvent: cannot do tls/ssl required by remote smtp server" );
281 if( !m_auth_mechanism.empty() ) msg.append(
": authentication will probably fail" ) ;
282 msg.append(
": try enabling client-tls" ) ;
288 m_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 = sPreprocessing ;
297 startPreprocessing() ;
299 else if( m_server_has_auth && m_sasl->active() && m_auth_mechanism.empty() )
301 throw NoMechanism( std::string() +
"add a client secret with mechanism " +
304 else if( m_server_has_auth && m_sasl->active() )
308 bool sensitive = false ;
309 std::string rsp = m_sasl->initial_response( m_auth_mechanism ,
310 done , error , sensitive ) ;
319 m_state = done ? sAuth2 : sAuth1 ;
320 send( rsp ,
false , sensitive ) ;
323 else if( !m_server_has_auth && m_sasl->active() && m_must_authenticate )
326 throw AuthenticationNotSupported() ;
330 m_state = sPreprocessing ;
331 startPreprocessing() ;
334 else if( m_state == sStartTls && reply.is(Reply::ServiceReady_220) )
336 m_sender.protocolSend( std::string() , 0U ,
true ) ;
338 else if( m_state == sStartTls && reply.is(Reply::NotAvailable_454) )
340 throw TlsError( reply.errorText() ) ;
342 else if( m_state == sStartTls && reply.is(Reply::Internal_2yy) )
344 m_state = sSentTlsEhlo ;
347 else if( m_state == sAuth1 && reply.is(Reply::Challenge_334) &&
G::Base64::valid(reply.text()) )
351 bool sensitive = false ;
352 std::string rsp = m_sasl->response( m_auth_mechanism ,
G::Base64::decode(reply.text()) ,
353 done , error , sensitive ) ;
361 m_state = done ? sAuth2 : m_state ;
365 else if( m_state == sAuth1 && reply.is(Reply::NotAuthenticated_535) )
367 if( m_must_authenticate )
368 throw AuthenticationError( std::string() +
369 "for \"" +
G::Str::printable(m_secrets.id(m_auth_mechanism)) +
"\" using " + m_auth_mechanism ) ;
371 m_state = sPreprocessing ;
372 startPreprocessing() ;
374 else if( m_state == sAuth2 )
376 m_authenticated_with_server = reply.is(Reply::Authenticated_235) ;
377 if( !m_authenticated_with_server && m_must_authenticate )
379 throw AuthenticationError( std::string() +
380 "for \"" +
G::Str::printable(m_secrets.id(m_auth_mechanism)) +
"\" using " + m_auth_mechanism ) ;
384 m_state = sPreprocessing ;
385 startPreprocessing() ;
388 else if( m_state == sPreprocessing && reply.is(Reply::Internal_2xx) )
390 m_state = sSentMail ;
393 else if( m_state == sPreprocessing && reply.is(Reply::Internal_2zz) )
396 protocol_done = true ;
397 raiseDoneSignal( std::string() , 1 ) ;
399 else if( m_state == sPreprocessing )
402 protocol_done = true ;
403 raiseDoneSignal( reply.errorText() , reply.value() ) ;
405 else if( m_state == sSentMail && reply.is(Reply::Ok_250) )
408 if( m_to.size() != 0U )
412 m_state = sSentRcpt ;
413 send(
"RCPT TO:<" , to ,
">" ) ;
415 else if( m_state == sSentRcpt && m_to.size() != 0U )
417 if( reply.positive() )
420 G_WARNING(
"GSmtp::ClientProtocol: recipient rejected" ) ;
422 std::string to = m_to.front() ;
425 send(
"RCPT TO:<" , to ,
">" ) ;
427 else if( m_state == sSentRcpt )
429 if( reply.positive() )
432 G_WARNING(
"GSmtp::ClientProtocol: recipient rejected" ) ;
434 if( ( m_must_accept_all_recipients && m_to_accepted != m_to_size ) || m_to_accepted == 0U )
436 m_state = sSentDataStub ;
441 m_state = sSentData ;
445 else if( m_state == sSentData && reply.is(Reply::OkForData_354) )
449 size_t n = sendLines() ;
451 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
459 else if( m_state == sSentDataStub )
462 protocol_done = true ;
463 std::string how_many = m_must_accept_all_recipients ? std::string(
"one or more") : std::string(
"all") ;
464 raiseDoneSignal( how_many +
" recipients rejected" , reply.value() ) ;
466 else if( m_state == sSentDot )
469 protocol_done = true ;
470 raiseDoneSignal( reply.errorText() , reply.value() ) ;
472 else if( is_start_event )
478 G_WARNING(
"GSmtp::ClientProtocol: failure in client protocol: state " << static_cast<int>(m_state)
480 throw ResponseError( reply.errorText() ) ;
482 return protocol_done ;
487 if( m_state == sStarted )
490 G_WARNING(
"GSmtp::ClientProtocol: timeout: no greeting from remote server: continuing" ) ;
491 m_state = sSentEhlo ;
494 else if( m_state == sPreprocessing )
497 raiseDoneSignal(
"preprocessing timeout" , 0 ,
true ) ;
502 raiseDoneSignal(
"response timeout" , 0 ,
true ) ;
508 if( m_state != sDone )
511 raiseDoneSignal( std::string(
"exception: ") + e.what() ) ;
517 return !reply.
textLine(
"AUTH ").empty() ;
520 G::Strings GSmtp::ClientProtocol::serverAuthMechanisms(
const ClientProtocolReply & reply )
const
523 std::string auth_line = reply.textLine(
"AUTH ") ;
524 if( ! auth_line.empty() )
533 void GSmtp::ClientProtocol::raiseDoneSignal(
const std::string & reason ,
int reason_code ,
bool warn )
535 if( ! reason.empty() && warn )
536 G_WARNING(
"GSmtp::ClientProtocol: " << reason ) ;
539 m_done_signal.emit( reason , reason_code ) ;
542 bool GSmtp::ClientProtocol::endOfContent()
const
544 return !m_content->good() ;
547 size_t GSmtp::ClientProtocol::sendLines()
552 std::string line( 200U ,
'.' ) ;
555 while( sendLine(line) )
560 bool GSmtp::ClientProtocol::sendLine( std::string & line )
565 std::istream & stream = *(m_content.get()) ;
568 const bool pre_erase = false ;
570 G_ASSERT( line.length() >= 1U && line.at(0U) ==
'.' ) ;
574 line.append( crlf() ) ;
575 bool all_sent = m_sender.protocolSend( line , line.at(1U) ==
'.' ? 0U : 1U , false ) ;
576 if( !all_sent && m_response_timeout != 0U )
577 startTimer( m_response_timeout ) ;
584 void GSmtp::ClientProtocol::send(
const char * p )
586 send( std::string(p) ,
false ,
false ) ;
589 void GSmtp::ClientProtocol::send(
const char * p ,
const std::string & s ,
const char * p2 )
591 std::string line( p ) ;
594 send( line ,
false ,
false ) ;
597 void GSmtp::ClientProtocol::send(
const char * p ,
const std::string & s )
599 send( std::string(p) + s ,
false ,
false ) ;
602 bool GSmtp::ClientProtocol::send(
const std::string & line ,
bool eot ,
bool sensitive )
604 if( m_response_timeout != 0U )
605 startTimer( m_response_timeout ) ;
607 std::string prefix( !eot && line.length() && line.at(0U) ==
'.' ?
"." :
"" ) ;
610 G_LOG(
"GSmtp::ClientProtocol: tx>>: [response not logged]" ) ;
616 return m_sender.protocolSend( prefix + line + crlf() , 0U ,
false ) ;
619 const std::string & GSmtp::ClientProtocol::crlf()
621 static const std::string s(
"\015\012" ) ;
627 return m_done_signal ;
632 return m_preprocessor_signal ;
641 if( line.length() >= 3U &&
642 is_digit(line.at(0U)) &&
643 line.at(0U) <=
'5' &&
644 is_digit(line.at(1U)) &&
645 is_digit(line.at(2U)) &&
646 ( line.length() == 3U || line.at(3U) ==
' ' || line.at(3U) ==
'-' ) )
649 m_complete = line.length() == 3U || line.at(3U) ==
' ' ;
651 if( line.length() > 4U )
653 m_text = line.substr(4U) ;
671 int i =
static_cast<int>(v) ;
673 std::ostringstream ss ;
677 G_ASSERT( reply.errorText().empty() ) ;
698 return ! m_complete ;
703 return m_valid && m_value < 400 ;
708 return m_valid ? m_value : 0 ;
713 return value() == v ;
718 const bool positive_completion = type() == PositiveCompletion ;
719 return positive_completion ? std::string() : ( m_text.empty() ? std::string(
"error") : m_text ) ;
729 size_t start_pos = m_text.find( std::string(
"\n")+prefix ) ;
730 if( start_pos == std::string::npos )
732 return std::string() ;
737 size_t end_pos = m_text.find(
"\n" , start_pos + prefix.length() ) ;
738 return m_text.substr( start_pos , end_pos-start_pos ) ;
742 bool GSmtp::ClientProtocolReply::is_digit(
char c )
744 return c >=
'0' && c <=
'9' ;
749 G_ASSERT( m_valid && (m_value/100) >= 1 && (m_value/100) <= 5 ) ;
750 return static_cast<Type>( m_value / 100 ) ;
755 G_ASSERT( m_valid && m_value >= 0 ) ;
756 int n = ( m_value / 10 ) % 10 ;
758 return static_cast<SubType>( n ) ;
760 return Invalid_SubType ;
769 m_complete = other.m_complete ;
770 m_text.append( std::string(
"\n") + other.
text() ) ;
771 return value() == other.
value() ;
776 std::string text( m_text ) ;
779 return text.find(key) != std::string::npos ;
791 unsigned int a ,
unsigned int b ,
unsigned int c ,
bool b1 ,
bool b2 ,
bool b3 ) :
792 thishost_name(name) ,
793 response_timeout(a) ,
795 preprocessor_timeout(c) ,
796 must_authenticate(b1) ,
797 must_accept_all_recipients(b2) ,
void secure()
To be called when the secure socket protocol has been successfully established.
static bool valid(const std::string &)
Returns true if the string can be decoded.
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
ClientProtocol(Sender &sender, const GAuth::Secrets &secrets, Config config)
Constructor.
Type type() const
Returns the reply type (category).
bool incomplete() const
Returns true if the reply is incomplete.
std::string errorText() const
Returns the text() string but with the guarantee that the returned string is empty if and only if the...
static int toInt(const std::string &s)
Converts string 's' to an int.
std::list< std::string > Strings
A std::list of std::strings.
virtual void onTimeoutException(std::exception &)
Final override from GNet::AbstractTimer.
static std::string encode(const std::string &)
Encodes the given string.
bool validFormat() const
Returns true if a valid format.
A structure containing GSmtp::ClientProtocol configuration parameters.
static void splitIntoTokens(const std::string &in, Strings &out, const std::string &ws)
Splits the string into 'ws'-delimited tokens.
static void trimLeft(std::string &s, const std::string &ws, size_type limit=0U)
Trims the lhs of s, taking off up to 'limit' of the 'ws' characters.
An interface used by ClientProtocol to send protocol messages.
bool is(Value v) const
Returns true if the reply value is 'v'.
G::Signal2< std::string, int > & doneSignal()
Returns a signal that is raised once the protocol has finished with a given message.
virtual void onTimeout()
Final override from GNet::AbstractTimer.
ClientProtocolReply(const std::string &line=std::string())
Constructor for one line of text.
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurences of sub-string 'from' with 'to'...
A simple interface to a store of secrets as used in authentication.
static ClientProtocolReply ok()
Factory function for an ok reply.
std::string textLine(const std::string &prefix) const
Returns a line of text() which starts with prefix.
void sendDone()
To be called when a blocked connection becomes unblocked.
void start(const std::string &from, const G::Strings &to, bool eight_bit, std::string authentication, std::string server_name, std::auto_ptr< std::istream > content)
Starts transmission of the given message.
int value() const
Returns the numeric value of the reply.
Part of the slot/signal system.
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 sslCapable()
Returns true if the implementation supports TLS/SSL.
SubType subType() const
Returns the reply sub-type.
bool apply(const std::string &rx)
Called on receipt of a line of text from the server.
A class for implementing the client-side SASL challenge/response concept.
Config(const std::string &, unsigned int, unsigned int, unsigned int, bool, bool, bool)
bool textContains(std::string s) const
Returns true if the text() contains the given substring.
bool add(const ClientProtocolReply &other)
Adds more lines to this reply.
static std::string decode(const std::string &)
Decodes the given string.
static ClientProtocolReply error(const std::string &reason)
Factory function for a generalised error reply.
bool positive() const
Returns true if the numeric value of the reply is less that four hundred.
G::Signal0 & preprocessorSignal()
Returns a signal that is raised when the protocol needs to do message preprocessing.
static std::string join(const Strings &strings, const std::string &sep)
Concatenates a set of strings.
static std::string encode(const std::string &s, const std::string &line_break)
Encodes the given string.
void preprocessorDone(bool ok, const std::string &reason)
To be called when the Preprocessor interface has done its thing.
A private implementation class used by ClientProtocol.
std::string text() const
Returns the complete text of the reply, excluding the numeric part, and with embedded newlines...
static void toUpper(std::string &s)
Replaces all lowercase characters in string 's' by uppercase characters.