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() )
307 send(
"AUTH " , m_auth_mechanism ) ;
309 else if( !m_server_has_auth && m_sasl->active() && m_must_authenticate )
312 throw AuthenticationNotSupported() ;
316 m_state = sPreprocessing ;
317 startPreprocessing() ;
320 else if( m_state == sStartTls && reply.is(Reply::ServiceReady_220) )
322 m_sender.protocolSend( std::string() , 0U ,
true ) ;
324 else if( m_state == sStartTls && reply.is(Reply::NotAvailable_454) )
326 throw TlsError( reply.errorText() ) ;
328 else if( m_state == sStartTls && reply.is(Reply::Internal_2yy) )
330 m_state = sSentTlsEhlo ;
333 else if( m_state == sAuth1 && reply.is(Reply::Challenge_334) &&
G::Base64::valid(reply.text()) )
337 bool sensitive = false ;
338 std::string rsp = m_sasl->response( m_auth_mechanism ,
G::Base64::decode(reply.text()) ,
339 done , error , sensitive ) ;
347 m_state = done ? sAuth2 : m_state ;
351 else if( m_state == sAuth1 && reply.is(Reply::NotAuthenticated_535) )
353 if( m_must_authenticate )
354 throw AuthenticationError( std::string() +
355 "for \"" +
G::Str::printable(m_secrets.id(m_auth_mechanism)) +
"\" using " + m_auth_mechanism ) ;
357 m_state = sPreprocessing ;
358 startPreprocessing() ;
360 else if( m_state == sAuth2 )
362 m_authenticated_with_server = reply.is(Reply::Authenticated_235) ;
363 if( !m_authenticated_with_server && m_must_authenticate )
365 throw AuthenticationError( std::string() +
366 "for \"" +
G::Str::printable(m_secrets.id(m_auth_mechanism)) +
"\" using " + m_auth_mechanism ) ;
370 m_state = sPreprocessing ;
371 startPreprocessing() ;
374 else if( m_state == sPreprocessing && reply.is(Reply::Internal_2xx) )
376 m_state = sSentMail ;
379 else if( m_state == sPreprocessing && reply.is(Reply::Internal_2zz) )
382 protocol_done = true ;
383 raiseDoneSignal( std::string() , 1 ) ;
385 else if( m_state == sPreprocessing )
388 protocol_done = true ;
389 raiseDoneSignal( reply.errorText() , reply.value() ) ;
391 else if( m_state == sSentMail && reply.is(Reply::Ok_250) )
394 if( m_to.size() != 0U )
398 m_state = sSentRcpt ;
399 send(
"RCPT TO:<" , to ,
">" ) ;
401 else if( m_state == sSentRcpt && m_to.size() != 0U )
403 if( reply.positive() )
406 G_WARNING(
"GSmtp::ClientProtocol: recipient rejected" ) ;
408 std::string to = m_to.front() ;
411 send(
"RCPT TO:<" , to ,
">" ) ;
413 else if( m_state == sSentRcpt )
415 if( reply.positive() )
418 G_WARNING(
"GSmtp::ClientProtocol: recipient rejected" ) ;
420 if( ( m_must_accept_all_recipients && m_to_accepted != m_to_size ) || m_to_accepted == 0U )
422 m_state = sSentDataStub ;
427 m_state = sSentData ;
431 else if( m_state == sSentData && reply.is(Reply::OkForData_354) )
435 size_t n = sendLines() ;
437 G_LOG(
"GSmtp::ClientProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
445 else if( m_state == sSentDataStub )
448 protocol_done = true ;
449 std::string how_many = m_must_accept_all_recipients ? std::string(
"one or more") : std::string(
"all") ;
450 raiseDoneSignal( how_many +
" recipients rejected" , reply.value() ) ;
452 else if( m_state == sSentDot )
455 protocol_done = true ;
456 raiseDoneSignal( reply.errorText() , reply.value() ) ;
458 else if( is_start_event )
464 G_WARNING(
"GSmtp::ClientProtocol: failure in client protocol: state " << static_cast<int>(m_state)
466 throw ResponseError( reply.errorText() ) ;
468 return protocol_done ;
473 if( m_state == sStarted )
476 G_WARNING(
"GSmtp::ClientProtocol: timeout: no greeting from remote server: continuing" ) ;
477 m_state = sSentEhlo ;
480 else if( m_state == sPreprocessing )
483 raiseDoneSignal(
"preprocessing timeout" , 0 ,
true ) ;
488 raiseDoneSignal(
"response timeout" , 0 ,
true ) ;
494 if( m_state != sDone )
497 raiseDoneSignal( std::string(
"exception: ") + e.what() ) ;
503 return !reply.
textLine(
"AUTH ").empty() ;
506 G::Strings GSmtp::ClientProtocol::serverAuthMechanisms(
const ClientProtocolReply & reply )
const
509 std::string auth_line = reply.textLine(
"AUTH ") ;
510 if( ! auth_line.empty() )
519 void GSmtp::ClientProtocol::raiseDoneSignal(
const std::string & reason ,
int reason_code ,
bool warn )
521 if( ! reason.empty() && warn )
522 G_WARNING(
"GSmtp::ClientProtocol: " << reason ) ;
525 m_done_signal.emit( reason , reason_code ) ;
528 bool GSmtp::ClientProtocol::endOfContent()
const
530 return !m_content->good() ;
533 size_t GSmtp::ClientProtocol::sendLines()
538 std::string line( 200U ,
'.' ) ;
541 while( sendLine(line) )
546 bool GSmtp::ClientProtocol::sendLine( std::string & line )
551 std::istream & stream = *(m_content.get()) ;
554 const bool pre_erase = false ;
556 G_ASSERT( line.length() >= 1U && line.at(0U) ==
'.' ) ;
560 line.append( crlf() ) ;
561 bool all_sent = m_sender.protocolSend( line , line.at(1U) ==
'.' ? 0U : 1U , false ) ;
562 if( !all_sent && m_response_timeout != 0U )
563 startTimer( m_response_timeout ) ;
570 void GSmtp::ClientProtocol::send(
const char * p )
572 send( std::string(p) ,
false ,
false ) ;
575 void GSmtp::ClientProtocol::send(
const char * p ,
const std::string & s ,
const char * p2 )
577 std::string line( p ) ;
580 send( line ,
false ,
false ) ;
583 void GSmtp::ClientProtocol::send(
const char * p ,
const std::string & s )
585 send( std::string(p) + s ,
false ,
false ) ;
588 bool GSmtp::ClientProtocol::send(
const std::string & line ,
bool eot ,
bool sensitive )
590 if( m_response_timeout != 0U )
591 startTimer( m_response_timeout ) ;
593 std::string prefix( !eot && line.length() && line.at(0U) ==
'.' ?
"." :
"" ) ;
596 G_LOG(
"GSmtp::ClientProtocol: tx>>: [response not logged]" ) ;
602 return m_sender.protocolSend( prefix + line + crlf() , 0U ,
false ) ;
605 const std::string & GSmtp::ClientProtocol::crlf()
607 static const std::string s(
"\015\012" ) ;
613 return m_done_signal ;
618 return m_preprocessor_signal ;
627 if( line.length() >= 3U &&
628 is_digit(line.at(0U)) &&
629 line.at(0U) <=
'5' &&
630 is_digit(line.at(1U)) &&
631 is_digit(line.at(2U)) &&
632 ( line.length() == 3U || line.at(3U) ==
' ' || line.at(3U) ==
'-' ) )
635 m_complete = line.length() == 3U || line.at(3U) ==
' ' ;
637 if( line.length() > 4U )
639 m_text = line.substr(4U) ;
657 int i =
static_cast<int>(v) ;
659 std::ostringstream ss ;
663 G_ASSERT( reply.errorText().empty() ) ;
684 return ! m_complete ;
689 return m_valid && m_value < 400 ;
694 return m_valid ? m_value : 0 ;
699 return value() == v ;
704 const bool positive_completion = type() == PositiveCompletion ;
705 return positive_completion ? std::string() : ( m_text.empty() ? std::string(
"error") : m_text ) ;
715 size_t start_pos = m_text.find( std::string(
"\n")+prefix ) ;
716 if( start_pos == std::string::npos )
718 return std::string() ;
723 size_t end_pos = m_text.find(
"\n" , start_pos + prefix.length() ) ;
724 return m_text.substr( start_pos , end_pos-start_pos ) ;
728 bool GSmtp::ClientProtocolReply::is_digit(
char c )
730 return c >=
'0' && c <=
'9' ;
735 G_ASSERT( m_valid && (m_value/100) >= 1 && (m_value/100) <= 5 ) ;
736 return static_cast<Type>( m_value / 100 ) ;
741 G_ASSERT( m_valid && m_value >= 0 ) ;
742 int n = ( m_value / 10 ) % 10 ;
744 return static_cast<SubType>( n ) ;
746 return Invalid_SubType ;
755 m_complete = other.m_complete ;
756 m_text.append( std::string(
"\n") + other.
text() ) ;
757 return value() == other.
value() ;
762 std::string text( m_text ) ;
765 return text.find(key) != std::string::npos ;
777 unsigned int a ,
unsigned int b ,
unsigned int c ,
bool b1 ,
bool b2 ,
bool b3 ) :
778 thishost_name(name) ,
779 response_timeout(a) ,
781 preprocessor_timeout(c) ,
782 must_authenticate(b1) ,
783 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.