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  bool done = true ;
307  bool error = false ;
308  bool sensitive = false ;
309  std::string rsp = m_sasl->initial_response( m_auth_mechanism ,
310  done , error , sensitive ) ;
311 
312  if( error )
313  {
314  m_state = sAuth2 ;
315  send( "*" ) ; // ie. cancel authentication
316  }
317  else
318  {
319  m_state = done ? sAuth2 : sAuth1 ;
320  send( rsp , false , sensitive ) ;
321  }
322  }
323  else if( !m_server_has_auth && m_sasl->active() && m_must_authenticate )
324  {
325  // (this makes sense if we need to propagate messages' authentication credentials)
326  throw AuthenticationNotSupported() ;
327  }
328  else
329  {
330  m_state = sPreprocessing ;
331  startPreprocessing() ;
332  }
333  }
334  else if( m_state == sStartTls && reply.is(Reply::ServiceReady_220) )
335  {
336  m_sender.protocolSend( std::string() , 0U , true ) ; // go secure
337  }
338  else if( m_state == sStartTls && reply.is(Reply::NotAvailable_454) )
339  {
340  throw TlsError( reply.errorText() ) ;
341  }
342  else if( m_state == sStartTls && reply.is(Reply::Internal_2yy) )
343  {
344  m_state = sSentTlsEhlo ;
345  sendEhlo() ;
346  }
347  else if( m_state == sAuth1 && reply.is(Reply::Challenge_334) && G::Base64::valid(reply.text()) )
348  {
349  bool done = true ;
350  bool error = false ;
351  bool sensitive = false ;
352  std::string rsp = m_sasl->response( m_auth_mechanism , G::Base64::decode(reply.text()) ,
353  done , error , sensitive ) ;
354  if( error )
355  {
356  m_state = sAuth2 ;
357  send( "*" ) ; // ie. cancel authentication
358  }
359  else
360  {
361  m_state = done ? sAuth2 : m_state ;
362  send( G::Base64::encode(rsp,std::string()) , false , sensitive ) ;
363  }
364  }
365  else if( m_state == sAuth1 && reply.is(Reply::NotAuthenticated_535) )
366  {
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 ) ;
370 
371  m_state = sPreprocessing ;
372  startPreprocessing() ; // (continue without sucessful authentication)
373  }
374  else if( m_state == sAuth2 )
375  {
376  m_authenticated_with_server = reply.is(Reply::Authenticated_235) ;
377  if( !m_authenticated_with_server && m_must_authenticate )
378  {
379  throw AuthenticationError( std::string() +
380  "for \"" + G::Str::printable(m_secrets.id(m_auth_mechanism)) + "\" using " + m_auth_mechanism ) ;
381  }
382  else
383  {
384  m_state = sPreprocessing ;
385  startPreprocessing() ; // (continue with or without sucessful authentication)
386  }
387  }
388  else if( m_state == sPreprocessing && reply.is(Reply::Internal_2xx) )
389  {
390  m_state = sSentMail ;
391  sendMail() ;
392  }
393  else if( m_state == sPreprocessing && reply.is(Reply::Internal_2zz) )
394  {
395  m_state = sDone ;
396  protocol_done = true ;
397  raiseDoneSignal( std::string() , 1 ) ; // TODO magic number
398  }
399  else if( m_state == sPreprocessing )
400  {
401  m_state = sDone ;
402  protocol_done = true ;
403  raiseDoneSignal( reply.errorText() , reply.value() ) ;
404  }
405  else if( m_state == sSentMail && reply.is(Reply::Ok_250) )
406  {
407  std::string to ;
408  if( m_to.size() != 0U ) // should always be non-zero due to message store guarantees
409  to = m_to.front() ;
410  m_to.pop_front() ;
411 
412  m_state = sSentRcpt ;
413  send( "RCPT TO:<" , to , ">" ) ;
414  }
415  else if( m_state == sSentRcpt && m_to.size() != 0U )
416  {
417  if( reply.positive() )
418  m_to_accepted++ ;
419  else
420  G_WARNING( "GSmtp::ClientProtocol: recipient rejected" ) ;
421 
422  std::string to = m_to.front() ;
423  m_to.pop_front() ;
424 
425  send( "RCPT TO:<" , to , ">" ) ;
426  }
427  else if( m_state == sSentRcpt )
428  {
429  if( reply.positive() )
430  m_to_accepted++ ;
431  else
432  G_WARNING( "GSmtp::ClientProtocol: recipient rejected" ) ;
433 
434  if( ( m_must_accept_all_recipients && m_to_accepted != m_to_size ) || m_to_accepted == 0U )
435  {
436  m_state = sSentDataStub ;
437  send( "RSET" ) ;
438  }
439  else
440  {
441  m_state = sSentData ;
442  send( "DATA" ) ;
443  }
444  }
445  else if( m_state == sSentData && reply.is(Reply::OkForData_354) )
446  {
447  m_state = sData ;
448 
449  size_t n = sendLines() ;
450 
451  G_LOG( "GSmtp::ClientProtocol: tx>>: [" << n << " line(s) of content]" ) ;
452 
453  if( endOfContent() )
454  {
455  m_state = sSentDot ;
456  send( "." , true ) ;
457  }
458  }
459  else if( m_state == sSentDataStub )
460  {
461  m_state = sDone ;
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() ) ;
465  }
466  else if( m_state == sSentDot )
467  {
468  m_state = sDone ;
469  protocol_done = true ;
470  raiseDoneSignal( reply.errorText() , reply.value() ) ;
471  }
472  else if( is_start_event )
473  {
474  throw NotReady() ;
475  }
476  else
477  {
478  G_WARNING( "GSmtp::ClientProtocol: failure in client protocol: state " << static_cast<int>(m_state)
479  << ": unexpected response [" << G::Str::printable(reply.text()) << "]" ) ;
480  throw ResponseError( reply.errorText() ) ;
481  }
482  return protocol_done ;
483 }
484 
486 {
487  if( m_state == sStarted )
488  {
489  // no 220 greeting seen -- go on regardless
490  G_WARNING( "GSmtp::ClientProtocol: timeout: no greeting from remote server: continuing" ) ;
491  m_state = sSentEhlo ;
492  sendEhlo() ;
493  }
494  else if( m_state == sPreprocessing )
495  {
496  m_state = sDone ;
497  raiseDoneSignal( "preprocessing timeout" , 0 , true ) ;
498  }
499  else
500  {
501  m_state = sDone ;
502  raiseDoneSignal( "response timeout" , 0 , true ) ;
503  }
504 }
505 
507 {
508  if( m_state != sDone )
509  {
510  m_state = sDone ;
511  raiseDoneSignal( std::string("exception: ") + e.what() ) ;
512  }
513 }
514 
515 bool GSmtp::ClientProtocol::serverAuth( const ClientProtocolReply & reply ) const
516 {
517  return !reply.textLine("AUTH ").empty() ;
518 }
519 
520 G::Strings GSmtp::ClientProtocol::serverAuthMechanisms( const ClientProtocolReply & reply ) const
521 {
522  G::Strings result ;
523  std::string auth_line = reply.textLine("AUTH ") ; // trailing space to avoid "AUTH="
524  if( ! auth_line.empty() )
525  {
526  G::Str::splitIntoTokens( auth_line , result , " " ) ;
527  if( result.size() )
528  result.pop_front() ; // remove "AUTH" ;
529  }
530  return result ;
531 }
532 
533 void GSmtp::ClientProtocol::raiseDoneSignal( const std::string & reason , int reason_code , bool warn )
534 {
535  if( ! reason.empty() && warn )
536  G_WARNING( "GSmtp::ClientProtocol: " << reason ) ;
537  cancelTimer() ;
538  m_content <<= 0 ;
539  m_done_signal.emit( reason , reason_code ) ;
540 }
541 
542 bool GSmtp::ClientProtocol::endOfContent() const
543 {
544  return !m_content->good() ;
545 }
546 
547 size_t GSmtp::ClientProtocol::sendLines()
548 {
549  cancelTimer() ; // no response expected during data transfer
550 
551  // the read buffer -- capacity grows to longest line, but start with something reasonable
552  std::string line( 200U , '.' ) ;
553 
554  size_t n = 0U ;
555  while( sendLine(line) )
556  n++ ;
557  return n ;
558 }
559 
560 bool GSmtp::ClientProtocol::sendLine( std::string & line )
561 {
562  line.erase( 1U ) ; // leave "."
563 
564  bool ok = false ;
565  std::istream & stream = *(m_content.get()) ;
566  if( stream.good() )
567  {
568  const bool pre_erase = false ;
569  G::Str::readLineFrom( stream , crlf() , line , pre_erase ) ;
570  G_ASSERT( line.length() >= 1U && line.at(0U) == '.' ) ;
571 
572  if( !stream.fail() )
573  {
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 ) ; // use response timer for when flow-control asserted
578  ok = all_sent ;
579  }
580  }
581  return ok ;
582 }
583 
584 void GSmtp::ClientProtocol::send( const char * p )
585 {
586  send( std::string(p) , false , false ) ;
587 }
588 
589 void GSmtp::ClientProtocol::send( const char * p , const std::string & s , const char * p2 )
590 {
591  std::string line( p ) ;
592  line.append( s ) ;
593  line.append( p2 ) ;
594  send( line , false , false ) ;
595 }
596 
597 void GSmtp::ClientProtocol::send( const char * p , const std::string & s )
598 {
599  send( std::string(p) + s , false , false ) ;
600 }
601 
602 bool GSmtp::ClientProtocol::send( const std::string & line , bool eot , bool sensitive )
603 {
604  if( m_response_timeout != 0U )
605  startTimer( m_response_timeout ) ;
606 
607  std::string prefix( !eot && line.length() && line.at(0U) == '.' ? "." : "" ) ;
608  if( sensitive )
609  {
610  G_LOG( "GSmtp::ClientProtocol: tx>>: [response not logged]" ) ;
611  }
612  else
613  {
614  G_LOG( "GSmtp::ClientProtocol: tx>>: \"" << prefix << G::Str::printable(line) << "\"" ) ;
615  }
616  return m_sender.protocolSend( prefix + line + crlf() , 0U , false ) ;
617 }
618 
619 const std::string & GSmtp::ClientProtocol::crlf()
620 {
621  static const std::string s( "\015\012" ) ;
622  return s ;
623 }
624 
626 {
627  return m_done_signal ;
628 }
629 
631 {
632  return m_preprocessor_signal ;
633 }
634 
635 // ===
636 
638  m_complete(false) ,
639  m_valid(false)
640 {
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) == '-' ) )
647  {
648  m_valid = true ;
649  m_complete = line.length() == 3U || line.at(3U) == ' ' ;
650  m_value = G::Str::toInt( line.substr(0U,3U) ) ;
651  if( line.length() > 4U )
652  {
653  m_text = line.substr(4U) ;
654  G::Str::trimLeft( m_text , " \t" ) ;
655  G::Str::replaceAll( m_text , "\t" , " " ) ;
656  }
657  }
658 }
659 
661 {
662  ClientProtocolReply reply( "250 OK" ) ;
663  G_ASSERT( ! reply.incomplete() ) ;
664  G_ASSERT( reply.positive() ) ;
665  G_ASSERT( reply.errorText().empty() ) ;
666  return reply ;
667 }
668 
670 {
671  int i = static_cast<int>(v) ;
672  G_ASSERT( i >= 200 && i <= 299 ) ;
673  std::ostringstream ss ;
674  ss << i << " OK" ;
675  ClientProtocolReply reply( ss.str() ) ;
676  G_ASSERT( reply.positive() ) ;
677  G_ASSERT( reply.errorText().empty() ) ;
678  G_ASSERT( reply.is(v) ) ;
679  return reply ;
680 }
681 
683 {
684  ClientProtocolReply reply( std::string("500 ")+G::Str::printable(reason) ) ;
685  G_ASSERT( ! reply.incomplete() ) ;
686  G_ASSERT( ! reply.positive() ) ;
687  G_ASSERT( ! reply.errorText().empty() ) ;
688  return reply ;
689 }
690 
692 {
693  return m_valid ;
694 }
695 
697 {
698  return ! m_complete ;
699 }
700 
702 {
703  return m_valid && m_value < 400 ;
704 }
705 
707 {
708  return m_valid ? m_value : 0 ;
709 }
710 
712 {
713  return value() == v ;
714 }
715 
717 {
718  const bool positive_completion = type() == PositiveCompletion ;
719  return positive_completion ? std::string() : ( m_text.empty() ? std::string("error") : m_text ) ;
720 }
721 
723 {
724  return m_text ;
725 }
726 
727 std::string GSmtp::ClientProtocolReply::textLine( const std::string & prefix ) const
728 {
729  size_t start_pos = m_text.find( std::string("\n")+prefix ) ;
730  if( start_pos == std::string::npos )
731  {
732  return std::string() ;
733  }
734  else
735  {
736  start_pos++ ;
737  size_t end_pos = m_text.find( "\n" , start_pos + prefix.length() ) ;
738  return m_text.substr( start_pos , end_pos-start_pos ) ;
739  }
740 }
741 
742 bool GSmtp::ClientProtocolReply::is_digit( char c )
743 {
744  return c >= '0' && c <= '9' ;
745 }
746 
748 {
749  G_ASSERT( m_valid && (m_value/100) >= 1 && (m_value/100) <= 5 ) ;
750  return static_cast<Type>( m_value / 100 ) ;
751 }
752 
754 {
755  G_ASSERT( m_valid && m_value >= 0 ) ;
756  int n = ( m_value / 10 ) % 10 ;
757  if( n < 4 )
758  return static_cast<SubType>( n ) ;
759  else
760  return Invalid_SubType ;
761 }
762 
764 {
765  G_ASSERT( other.m_valid ) ;
766  G_ASSERT( m_valid ) ;
767  G_ASSERT( !m_complete ) ;
768 
769  m_complete = other.m_complete ;
770  m_text.append( std::string("\n") + other.text() ) ;
771  return value() == other.value() ;
772 }
773 
774 bool GSmtp::ClientProtocolReply::textContains( std::string key ) const
775 {
776  std::string text( m_text ) ;
777  G::Str::toUpper( key ) ;
778  G::Str::toUpper( text ) ;
779  return text.find(key) != std::string::npos ;
780 }
781 
782 // ===
783 
785 {
786 }
787 
788 // ===
789 
790 GSmtp::ClientProtocol::Config::Config( const std::string & name ,
791  unsigned int a , unsigned int b , unsigned int c , bool b1 , bool b2 , bool b3 ) :
792  thishost_name(name) ,
793  response_timeout(a) ,
794  ready_timeout(b) ,
795  preprocessor_timeout(c) ,
796  must_authenticate(b1) ,
797  must_accept_all_recipients(b2) ,
798  eight_bit_strict(b3)
799 {
800 }
801 
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