gclientprotocol.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2013 Graeme Walker <graeme_walker@users.sourceforge.net>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // gclientprotocol.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gnet.h"
23 #include "gsmtp.h"
24 #include "glocal.h"
25 #include "gfile.h"
26 #include "gsaslclient.h"
27 #include "gbase64.h"
28 #include "gstr.h"
29 #include "gmemory.h"
30 #include "gxtext.h"
31 #include "gclientprotocol.h"
32 #include "gsocketprotocol.h"
33 #include "gresolver.h"
34 #include "glog.h"
35 #include "gassert.h"
36 
37 GSmtp::ClientProtocol::ClientProtocol( Sender & sender , const GAuth::Secrets & secrets , Config config ) :
38  m_sender(sender) ,
39  m_secrets(secrets) ,
40  m_thishost(config.thishost_name) ,
41  m_state(sInit) ,
42  m_to_size(0U) ,
43  m_to_accepted(0U) ,
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) ,
52  m_warned(false) ,
53  m_response_timeout(config.response_timeout) ,
54  m_ready_timeout(config.ready_timeout) ,
55  m_preprocessor_timeout(config.preprocessor_timeout) ,
56  m_done_signal(true)
57 {
58 }
59 
60 void GSmtp::ClientProtocol::start( const std::string & from , const G::Strings & to , bool eight_bit ,
61  std::string authentication , std::string server_name , std::auto_ptr<std::istream> content )
62 {
63  G_DEBUG( "GSmtp::ClientProtocol::start" ) ;
64 
65  // reinitialise for the new message & server
66  m_to = to ;
67  m_to_size = to.size() ;
68  m_to_accepted = 0U ;
69  m_from = from ;
70  m_content = content ;
71  m_message_is_8bit = eight_bit ;
72  m_message_authentication = authentication ;
73  m_reply = Reply() ;
74  m_sasl <<= new GAuth::SaslClient( m_secrets , server_name ) ;
75  m_done_signal.reset() ;
76 
77  // (re)start the protocol
78  applyEvent( Reply() , true ) ;
79 }
80 
81 void GSmtp::ClientProtocol::preprocessorDone( bool ok , const std::string & reason )
82 {
83  if( ok )
84  {
85  // dummy event to continue with this message
86  applyEvent( Reply::ok(Reply::Internal_2xx) ) ;
87  }
88  else if( reason.empty() )
89  {
90  // dummy event to abandon this message
91  applyEvent( Reply::ok(Reply::Internal_2zz) ) ;
92  }
93  else
94  {
95  std::string error = "preprocessing: " + reason ;
96  applyEvent( Reply::error(error) ) ;
97  }
98 }
99 
101 {
102  // convert the event into a pretend smtp Reply
103  applyEvent( Reply::ok(Reply::Internal_2yy) ) ;
104 }
105 
107 {
108  if( m_state == sData )
109  {
110  size_t n = sendLines() ;
111 
112  G_LOG( "GSmtp::ClientProtocol: tx>>: [" << n << " line(s) of content]" ) ;
113  if( endOfContent() )
114  {
115  m_state = sSentDot ;
116  send( "." , true ) ;
117  }
118  }
119 }
120 
121 bool GSmtp::ClientProtocol::parseReply( Reply & stored_reply , const std::string & rx , std::string & reason )
122 {
123  Reply this_reply = Reply( rx ) ;
124  if( ! this_reply.validFormat() )
125  {
126  stored_reply = Reply() ;
127  reason = "invalid reply format" ;
128  return false ;
129  }
130  else if( stored_reply.validFormat() && stored_reply.incomplete() )
131  {
132  if( ! stored_reply.add(this_reply) )
133  {
134  stored_reply = Reply() ;
135  reason = "invalid continuation line" ;
136  return false ;
137  }
138  }
139  else
140  {
141  stored_reply = this_reply ;
142  }
143  return ! stored_reply.incomplete() ;
144 }
145 
146 bool GSmtp::ClientProtocol::apply( const std::string & rx )
147 {
148  G_LOG( "GSmtp::ClientProtocol: rx<<: \"" << G::Str::printable(rx) << "\"" ) ;
149 
150  std::string reason ;
151  bool protocol_done = false ;
152  bool complete_reply = parseReply( m_reply , rx , reason ) ;
153  if( complete_reply )
154  {
155  protocol_done = applyEvent( m_reply ) ;
156  }
157  else
158  {
159  if( reason.length() != 0U )
160  send( "550 syntax error: " , reason ) ;
161  }
162  return protocol_done ;
163 }
164 
165 void GSmtp::ClientProtocol::sendEhlo()
166 {
167  send( "EHLO " , m_thishost ) ;
168 }
169 
170 void GSmtp::ClientProtocol::sendHelo()
171 {
172  send( "HELO " , m_thishost ) ;
173 }
174 
175 void GSmtp::ClientProtocol::sendMail()
176 {
177  const bool dodgy = m_message_is_8bit && !m_server_has_8bitmime ;
178  if( dodgy && m_strict )
179  {
180  m_state = sDone ;
181  raiseDoneSignal( "cannot send 8-bit message to 7-bit server" , 0 , true ) ;
182  }
183  else
184  {
185  if( dodgy && !m_warned )
186  {
187  m_warned = true ;
188  G_WARNING( "GSmtp::ClientProtocol::sendMail: sending an eight-bit message "
189  "to a server which has not advertised the 8BITMIME extension" ) ;
190  }
191  sendMailCore() ;
192  }
193 }
194 
195 void GSmtp::ClientProtocol::sendMailCore()
196 {
197  std::string mail_from_tail = m_from ;
198  mail_from_tail.append( 1U , '>' ) ;
199  if( m_server_has_8bitmime )
200  {
201  mail_from_tail.append( " BODY=8BITMIME" ) ;
202  }
203  if( m_authenticated_with_server && !m_message_authentication.empty() )
204  {
205  mail_from_tail.append( " AUTH=" ) ;
206  mail_from_tail.append( G::Xtext::encode(m_message_authentication) ) ;
207  }
208  else if( m_authenticated_with_server )
209  {
210  mail_from_tail.append( " AUTH=<>" ) ;
211  }
212  send( "MAIL FROM:<" , mail_from_tail ) ;
213 }
214 
215 void GSmtp::ClientProtocol::startPreprocessing()
216 {
217  G_ASSERT( m_state == sPreprocessing ) ;
218  if( m_preprocessor_timeout != 0U )
219  startTimer( m_preprocessor_timeout ) ;
220  m_preprocessor_signal.emit() ;
221 }
222 
223 bool GSmtp::ClientProtocol::applyEvent( const Reply & reply , bool is_start_event )
224 {
225  G_DEBUG( "GSmtp::ClientProtocol::applyEvent: " << reply.value() << ": " << reply.text() ) ;
226 
227  cancelTimer() ;
228 
229  bool protocol_done = false ;
230  if( m_state == sInit && is_start_event )
231  {
232  // wait for 220 greeting
233  m_state = sStarted ;
234  if( m_ready_timeout != 0U )
235  startTimer( m_ready_timeout ) ;
236  }
237  else if( m_state == sServiceReady && is_start_event )
238  {
239  // already got greeting
240  m_state = sSentEhlo ;
241  sendEhlo() ;
242  }
243  else if( m_state == sDone && is_start_event )
244  {
245  m_state = sPreprocessing ;
246  startPreprocessing() ;
247  }
248  else if( m_state == sInit && reply.is(Reply::ServiceReady_220) )
249  {
250  G_DEBUG( "GSmtp::ClientProtocol::applyEvent: init -> ready" ) ;
251  m_state = sServiceReady ;
252  }
253  else if( m_state == sStarted && reply.is(Reply::ServiceReady_220) )
254  {
255  G_DEBUG( "GSmtp::ClientProtocol::applyEvent: start -> sent-ehlo" ) ;
256  m_state = sSentEhlo ;
257  sendEhlo() ;
258  }
259  else if( m_state == sSentEhlo && (
260  reply.is(Reply::SyntaxError_500) ||
261  reply.is(Reply::SyntaxError_501) ||
262  reply.is(Reply::NotImplemented_502) ) )
263  {
264  // it didn't like EHLO so fall back to HELO
265  m_state = sSentHelo ;
266  sendHelo() ;
267  }
268  else if( ( m_state == sSentEhlo || m_state == sSentHelo || m_state == sSentTlsEhlo ) && reply.is(Reply::Ok_250) )
269  {
270  G_ASSERT( m_sasl.get() != NULL ) ;
271  G_DEBUG( "GSmtp::ClientProtocol::applyEvent: ehlo reply \"" << G::Str::printable(reply.text()) << "\"" ) ;
272 
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) ) ;
277 
278  if( m_server_has_tls && !GNet::SocketProtocol::sslCapable() )
279  {
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" ) ;
283  G_WARNING( msg ) ;
284  }
285 
286  if( m_state == sSentEhlo && m_server_has_tls && GNet::SocketProtocol::sslCapable() )
287  {
288  m_state = sStartTls ;
289  send( "STARTTLS" ) ;
290  }
291  else if( m_server_has_auth && !m_sasl->active() )
292  {
293  // continue -- the server will complain later if it considers authentication is mandatory
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() ;
298  }
299  else if( m_server_has_auth && m_sasl->active() && m_auth_mechanism.empty() )
300  {
301  throw NoMechanism( std::string() + "add a client secret with mechanism " +
302  G::Str::printable(G::Str::join(serverAuthMechanisms(reply),"/")) ) ;
303  }
304  else if( m_server_has_auth && m_sasl->active() )
305  {
306  m_state = sAuth1 ;
307  send( "AUTH " , m_auth_mechanism ) ;
308  }
309  else if( !m_server_has_auth && m_sasl->active() && m_must_authenticate )
310  {
311  // (this makes sense if we need to propagate messages' authentication credentials)
312  throw AuthenticationNotSupported() ;
313  }
314  else
315  {
316  m_state = sPreprocessing ;
317  startPreprocessing() ;
318  }
319  }
320  else if( m_state == sStartTls && reply.is(Reply::ServiceReady_220) )
321  {
322  m_sender.protocolSend( std::string() , 0U , true ) ; // go secure
323  }
324  else if( m_state == sStartTls && reply.is(Reply::NotAvailable_454) )
325  {
326  throw TlsError( reply.errorText() ) ;
327  }
328  else if( m_state == sStartTls && reply.is(Reply::Internal_2yy) )
329  {
330  m_state = sSentTlsEhlo ;
331  sendEhlo() ;
332  }
333  else if( m_state == sAuth1 && reply.is(Reply::Challenge_334) && G::Base64::valid(reply.text()) )
334  {
335  bool done = true ;
336  bool error = false ;
337  bool sensitive = false ;
338  std::string rsp = m_sasl->response( m_auth_mechanism , G::Base64::decode(reply.text()) ,
339  done , error , sensitive ) ;
340  if( error )
341  {
342  m_state = sAuth2 ;
343  send( "*" ) ; // ie. cancel authentication
344  }
345  else
346  {
347  m_state = done ? sAuth2 : m_state ;
348  send( G::Base64::encode(rsp,std::string()) , false , sensitive ) ;
349  }
350  }
351  else if( m_state == sAuth1 && reply.is(Reply::NotAuthenticated_535) )
352  {
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 ) ;
356 
357  m_state = sPreprocessing ;
358  startPreprocessing() ; // (continue without sucessful authentication)
359  }
360  else if( m_state == sAuth2 )
361  {
362  m_authenticated_with_server = reply.is(Reply::Authenticated_235) ;
363  if( !m_authenticated_with_server && m_must_authenticate )
364  {
365  throw AuthenticationError( std::string() +
366  "for \"" + G::Str::printable(m_secrets.id(m_auth_mechanism)) + "\" using " + m_auth_mechanism ) ;
367  }
368  else
369  {
370  m_state = sPreprocessing ;
371  startPreprocessing() ; // (continue with or without sucessful authentication)
372  }
373  }
374  else if( m_state == sPreprocessing && reply.is(Reply::Internal_2xx) )
375  {
376  m_state = sSentMail ;
377  sendMail() ;
378  }
379  else if( m_state == sPreprocessing && reply.is(Reply::Internal_2zz) )
380  {
381  m_state = sDone ;
382  protocol_done = true ;
383  raiseDoneSignal( std::string() , 1 ) ; // TODO magic number
384  }
385  else if( m_state == sPreprocessing )
386  {
387  m_state = sDone ;
388  protocol_done = true ;
389  raiseDoneSignal( reply.errorText() , reply.value() ) ;
390  }
391  else if( m_state == sSentMail && reply.is(Reply::Ok_250) )
392  {
393  std::string to ;
394  if( m_to.size() != 0U ) // should always be non-zero due to message store guarantees
395  to = m_to.front() ;
396  m_to.pop_front() ;
397 
398  m_state = sSentRcpt ;
399  send( "RCPT TO:<" , to , ">" ) ;
400  }
401  else if( m_state == sSentRcpt && m_to.size() != 0U )
402  {
403  if( reply.positive() )
404  m_to_accepted++ ;
405  else
406  G_WARNING( "GSmtp::ClientProtocol: recipient rejected" ) ;
407 
408  std::string to = m_to.front() ;
409  m_to.pop_front() ;
410 
411  send( "RCPT TO:<" , to , ">" ) ;
412  }
413  else if( m_state == sSentRcpt )
414  {
415  if( reply.positive() )
416  m_to_accepted++ ;
417  else
418  G_WARNING( "GSmtp::ClientProtocol: recipient rejected" ) ;
419 
420  if( ( m_must_accept_all_recipients && m_to_accepted != m_to_size ) || m_to_accepted == 0U )
421  {
422  m_state = sSentDataStub ;
423  send( "RSET" ) ;
424  }
425  else
426  {
427  m_state = sSentData ;
428  send( "DATA" ) ;
429  }
430  }
431  else if( m_state == sSentData && reply.is(Reply::OkForData_354) )
432  {
433  m_state = sData ;
434 
435  size_t n = sendLines() ;
436 
437  G_LOG( "GSmtp::ClientProtocol: tx>>: [" << n << " line(s) of content]" ) ;
438 
439  if( endOfContent() )
440  {
441  m_state = sSentDot ;
442  send( "." , true ) ;
443  }
444  }
445  else if( m_state == sSentDataStub )
446  {
447  m_state = sDone ;
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() ) ;
451  }
452  else if( m_state == sSentDot )
453  {
454  m_state = sDone ;
455  protocol_done = true ;
456  raiseDoneSignal( reply.errorText() , reply.value() ) ;
457  }
458  else if( is_start_event )
459  {
460  throw NotReady() ;
461  }
462  else
463  {
464  G_WARNING( "GSmtp::ClientProtocol: failure in client protocol: state " << static_cast<int>(m_state)
465  << ": unexpected response [" << G::Str::printable(reply.text()) << "]" ) ;
466  throw ResponseError( reply.errorText() ) ;
467  }
468  return protocol_done ;
469 }
470 
472 {
473  if( m_state == sStarted )
474  {
475  // no 220 greeting seen -- go on regardless
476  G_WARNING( "GSmtp::ClientProtocol: timeout: no greeting from remote server: continuing" ) ;
477  m_state = sSentEhlo ;
478  sendEhlo() ;
479  }
480  else if( m_state == sPreprocessing )
481  {
482  m_state = sDone ;
483  raiseDoneSignal( "preprocessing timeout" , 0 , true ) ;
484  }
485  else
486  {
487  m_state = sDone ;
488  raiseDoneSignal( "response timeout" , 0 , true ) ;
489  }
490 }
491 
493 {
494  if( m_state != sDone )
495  {
496  m_state = sDone ;
497  raiseDoneSignal( std::string("exception: ") + e.what() ) ;
498  }
499 }
500 
501 bool GSmtp::ClientProtocol::serverAuth( const ClientProtocolReply & reply ) const
502 {
503  return !reply.textLine("AUTH ").empty() ;
504 }
505 
506 G::Strings GSmtp::ClientProtocol::serverAuthMechanisms( const ClientProtocolReply & reply ) const
507 {
508  G::Strings result ;
509  std::string auth_line = reply.textLine("AUTH ") ; // trailing space to avoid "AUTH="
510  if( ! auth_line.empty() )
511  {
512  G::Str::splitIntoTokens( auth_line , result , " " ) ;
513  if( result.size() )
514  result.pop_front() ; // remove "AUTH" ;
515  }
516  return result ;
517 }
518 
519 void GSmtp::ClientProtocol::raiseDoneSignal( const std::string & reason , int reason_code , bool warn )
520 {
521  if( ! reason.empty() && warn )
522  G_WARNING( "GSmtp::ClientProtocol: " << reason ) ;
523  cancelTimer() ;
524  m_content <<= 0 ;
525  m_done_signal.emit( reason , reason_code ) ;
526 }
527 
528 bool GSmtp::ClientProtocol::endOfContent() const
529 {
530  return !m_content->good() ;
531 }
532 
533 size_t GSmtp::ClientProtocol::sendLines()
534 {
535  cancelTimer() ; // no response expected during data transfer
536 
537  // the read buffer -- capacity grows to longest line, but start with something reasonable
538  std::string line( 200U , '.' ) ;
539 
540  size_t n = 0U ;
541  while( sendLine(line) )
542  n++ ;
543  return n ;
544 }
545 
546 bool GSmtp::ClientProtocol::sendLine( std::string & line )
547 {
548  line.erase( 1U ) ; // leave "."
549 
550  bool ok = false ;
551  std::istream & stream = *(m_content.get()) ;
552  if( stream.good() )
553  {
554  const bool pre_erase = false ;
555  G::Str::readLineFrom( stream , crlf() , line , pre_erase ) ;
556  G_ASSERT( line.length() >= 1U && line.at(0U) == '.' ) ;
557 
558  if( !stream.fail() )
559  {
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 ) ; // use response timer for when flow-control asserted
564  ok = all_sent ;
565  }
566  }
567  return ok ;
568 }
569 
570 void GSmtp::ClientProtocol::send( const char * p )
571 {
572  send( std::string(p) , false , false ) ;
573 }
574 
575 void GSmtp::ClientProtocol::send( const char * p , const std::string & s , const char * p2 )
576 {
577  std::string line( p ) ;
578  line.append( s ) ;
579  line.append( p2 ) ;
580  send( line , false , false ) ;
581 }
582 
583 void GSmtp::ClientProtocol::send( const char * p , const std::string & s )
584 {
585  send( std::string(p) + s , false , false ) ;
586 }
587 
588 bool GSmtp::ClientProtocol::send( const std::string & line , bool eot , bool sensitive )
589 {
590  if( m_response_timeout != 0U )
591  startTimer( m_response_timeout ) ;
592 
593  std::string prefix( !eot && line.length() && line.at(0U) == '.' ? "." : "" ) ;
594  if( sensitive )
595  {
596  G_LOG( "GSmtp::ClientProtocol: tx>>: [response not logged]" ) ;
597  }
598  else
599  {
600  G_LOG( "GSmtp::ClientProtocol: tx>>: \"" << prefix << G::Str::printable(line) << "\"" ) ;
601  }
602  return m_sender.protocolSend( prefix + line + crlf() , 0U , false ) ;
603 }
604 
605 const std::string & GSmtp::ClientProtocol::crlf()
606 {
607  static const std::string s( "\015\012" ) ;
608  return s ;
609 }
610 
612 {
613  return m_done_signal ;
614 }
615 
617 {
618  return m_preprocessor_signal ;
619 }
620 
621 // ===
622 
624  m_complete(false) ,
625  m_valid(false)
626 {
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) == '-' ) )
633  {
634  m_valid = true ;
635  m_complete = line.length() == 3U || line.at(3U) == ' ' ;
636  m_value = G::Str::toInt( line.substr(0U,3U) ) ;
637  if( line.length() > 4U )
638  {
639  m_text = line.substr(4U) ;
640  G::Str::trimLeft( m_text , " \t" ) ;
641  G::Str::replaceAll( m_text , "\t" , " " ) ;
642  }
643  }
644 }
645 
647 {
648  ClientProtocolReply reply( "250 OK" ) ;
649  G_ASSERT( ! reply.incomplete() ) ;
650  G_ASSERT( reply.positive() ) ;
651  G_ASSERT( reply.errorText().empty() ) ;
652  return reply ;
653 }
654 
656 {
657  int i = static_cast<int>(v) ;
658  G_ASSERT( i >= 200 && i <= 299 ) ;
659  std::ostringstream ss ;
660  ss << i << " OK" ;
661  ClientProtocolReply reply( ss.str() ) ;
662  G_ASSERT( reply.positive() ) ;
663  G_ASSERT( reply.errorText().empty() ) ;
664  G_ASSERT( reply.is(v) ) ;
665  return reply ;
666 }
667 
669 {
670  ClientProtocolReply reply( std::string("500 ")+G::Str::printable(reason) ) ;
671  G_ASSERT( ! reply.incomplete() ) ;
672  G_ASSERT( ! reply.positive() ) ;
673  G_ASSERT( ! reply.errorText().empty() ) ;
674  return reply ;
675 }
676 
678 {
679  return m_valid ;
680 }
681 
683 {
684  return ! m_complete ;
685 }
686 
688 {
689  return m_valid && m_value < 400 ;
690 }
691 
693 {
694  return m_valid ? m_value : 0 ;
695 }
696 
698 {
699  return value() == v ;
700 }
701 
703 {
704  const bool positive_completion = type() == PositiveCompletion ;
705  return positive_completion ? std::string() : ( m_text.empty() ? std::string("error") : m_text ) ;
706 }
707 
709 {
710  return m_text ;
711 }
712 
713 std::string GSmtp::ClientProtocolReply::textLine( const std::string & prefix ) const
714 {
715  size_t start_pos = m_text.find( std::string("\n")+prefix ) ;
716  if( start_pos == std::string::npos )
717  {
718  return std::string() ;
719  }
720  else
721  {
722  start_pos++ ;
723  size_t end_pos = m_text.find( "\n" , start_pos + prefix.length() ) ;
724  return m_text.substr( start_pos , end_pos-start_pos ) ;
725  }
726 }
727 
728 bool GSmtp::ClientProtocolReply::is_digit( char c )
729 {
730  return c >= '0' && c <= '9' ;
731 }
732 
734 {
735  G_ASSERT( m_valid && (m_value/100) >= 1 && (m_value/100) <= 5 ) ;
736  return static_cast<Type>( m_value / 100 ) ;
737 }
738 
740 {
741  G_ASSERT( m_valid && m_value >= 0 ) ;
742  int n = ( m_value / 10 ) % 10 ;
743  if( n < 4 )
744  return static_cast<SubType>( n ) ;
745  else
746  return Invalid_SubType ;
747 }
748 
750 {
751  G_ASSERT( other.m_valid ) ;
752  G_ASSERT( m_valid ) ;
753  G_ASSERT( !m_complete ) ;
754 
755  m_complete = other.m_complete ;
756  m_text.append( std::string("\n") + other.text() ) ;
757  return value() == other.value() ;
758 }
759 
760 bool GSmtp::ClientProtocolReply::textContains( std::string key ) const
761 {
762  std::string text( m_text ) ;
763  G::Str::toUpper( key ) ;
764  G::Str::toUpper( text ) ;
765  return text.find(key) != std::string::npos ;
766 }
767 
768 // ===
769 
771 {
772 }
773 
774 // ===
775 
776 GSmtp::ClientProtocol::Config::Config( const std::string & name ,
777  unsigned int a , unsigned int b , unsigned int c , bool b1 , bool b2 , bool b3 ) :
778  thishost_name(name) ,
779  response_timeout(a) ,
780  ready_timeout(b) ,
781  preprocessor_timeout(c) ,
782  must_authenticate(b1) ,
783  must_accept_all_recipients(b2) ,
784  eight_bit_strict(b3)
785 {
786 }
787 
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.
Definition: gbase64.cpp:166
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:507
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.
Definition: gstr.cpp:310
std::list< std::string > Strings
A std::list of std::strings.
Definition: gstrings.h:39
virtual void onTimeoutException(std::exception &)
Final override from GNet::AbstractTimer.
static std::string encode(const std::string &)
Encodes the given string.
Definition: gxtext.cpp:58
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.
Definition: gstr.cpp:714
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.
Definition: gstr.cpp:111
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.
#define G_ASSERT(test)
Definition: gassert.h:30
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'...
Definition: gstr.cpp:78
A simple interface to a store of secrets as used in authentication.
Definition: gsecrets.h:44
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.
#define G_LOG(expr)
Definition: glog.h:98
int value() const
Returns the numeric value of the reply.
Part of the slot/signal system.
Definition: gslot.h:138
static std::string readLineFrom(std::istream &stream, const std::string &eol=std::string())
Reads a line from the stream using the given line terminator.
Definition: gstr.cpp:536
#define G_DEBUG(expr)
Definition: glog.h:95
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.
Definition: gsaslclient.h:48
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.
Definition: gbase64.cpp:131
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.
Definition: gstr.cpp:799
static std::string encode(const std::string &s, const std::string &line_break)
Encodes the given string.
Definition: gbase64.cpp:68
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.
#define G_WARNING(expr)
Definition: glog.h:107
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.
Definition: gstr.cpp:422