- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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
100 void GSmtp::ClientProtocol::secure()
101 {
102 // convert the event into a pretend smtp Reply
103 applyEvent( Reply::ok(Reply::Internal_2yy) ) ;
104 }
105
106 void GSmtp::ClientProtocol::sendDone()
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
485 void GSmtp::ClientProtocol::onTimeout()
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
506 void GSmtp::ClientProtocol::onTimeoutException( std::exception & e )
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
625 G::Signal2<std::string,int> & GSmtp::ClientProtocol::doneSignal()
626 {
627 return m_done_signal ;
628 }
629
630 G::Signal0 & GSmtp::ClientProtocol::preprocessorSignal()
631 {
632 return m_preprocessor_signal ;
633 }
634
635 // ===
636
637 GSmtp::ClientProtocolReply::ClientProtocolReply( const std::string & line ) :
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
660 GSmtp::ClientProtocolReply GSmtp::ClientProtocolReply::ok()
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
669 GSmtp::ClientProtocolReply GSmtp::ClientProtocolReply::ok( Value v )
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
682 GSmtp::ClientProtocolReply GSmtp::ClientProtocolReply::error( const std::string & reason )
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
691 bool GSmtp::ClientProtocolReply::validFormat() const
692 {
693 return m_valid ;
694 }
695
696 bool GSmtp::ClientProtocolReply::incomplete() const
697 {
698 return ! m_complete ;
699 }
700
701 bool GSmtp::ClientProtocolReply::positive() const
702 {
703 return m_valid && m_value < 400 ;
704 }
705
706 int GSmtp::ClientProtocolReply::value() const
707 {
708 return m_valid ? m_value : 0 ;
709 }
710
711 bool GSmtp::ClientProtocolReply::is( Value v ) const
712 {
713 return value() == v ;
714 }
715
716 std::string GSmtp::ClientProtocolReply::errorText() const
717 {
718 const bool positive_completion = type() == PositiveCompletion ;
719 return positive_completion ? std::string() : ( m_text.empty() ? std::string("error") : m_text ) ;
720 }
721
722 std::string GSmtp::ClientProtocolReply::text() const
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
747 GSmtp::ClientProtocolReply::Type GSmtp::ClientProtocolReply::type() const
748 {
749 G_ASSERT( m_valid && (m_value/100) >= 1 && (m_value/100) <= 5 ) ;
750 return static_cast<Type>( m_value / 100 ) ;
751 }
752
753 GSmtp::ClientProtocolReply::SubType GSmtp::ClientProtocolReply::subType() const
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
763 bool GSmtp::ClientProtocolReply::add( const ClientProtocolReply & other )
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
784 GSmtp::ClientProtocol::Sender::~Sender()
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
GSmtp::ClientProtocol::secure
void secure()
To be called when the secure socket protocol has been successfully established.
Definition: gclientprotocol.cpp:100
G::Base64::valid
static bool valid(const std::string &)
Returns true if the string can be decoded.
Definition: gbase64.cpp:166
G::Signal2< std::string, int >
G::Str::printable
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:507
GSmtp::ClientProtocol::ClientProtocol
ClientProtocol(Sender &sender, const GAuth::Secrets &secrets, Config config)
Constructor.
Definition: gclientprotocol.cpp:37
GSmtp::ClientProtocolReply::type
Type type() const
Returns the reply type (category).
Definition: gclientprotocol.cpp:747
gsaslclient.h
glog.h
GSmtp::ClientProtocolReply::incomplete
bool incomplete() const
Returns true if the reply is incomplete.
Definition: gclientprotocol.cpp:696
GSmtp::ClientProtocolReply::errorText
std::string errorText() const
Returns the text() string but with the guarantee that the returned string is empty if and only if the...
Definition: gclientprotocol.cpp:716
G::Str::toInt
static int toInt(const std::string &s)
Converts string 's' to an int.
Definition: gstr.cpp:310
G::Strings
std::list< std::string > Strings
A std::list of std::strings.
Definition: gstrings.h:39
GSmtp::ClientProtocol::onTimeoutException
virtual void onTimeoutException(std::exception &)
Final override from GNet::AbstractTimer.
Definition: gclientprotocol.cpp:506
gsocketprotocol.h
G::Xtext::encode
static std::string encode(const std::string &)
Encodes the given string.
Definition: gxtext.cpp:58
GSmtp::ClientProtocolReply::validFormat
bool validFormat() const
Returns true if a valid format.
Definition: gclientprotocol.cpp:691
GSmtp::ClientProtocol::Config
A structure containing GSmtp::ClientProtocol configuration parameters.
Definition: gclientprotocol.h:193
G::Str::splitIntoTokens
static void splitIntoTokens(const std::string &in, Strings &out, const std::string &ws)
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:714
GSmtp::ClientProtocolReply::Type
Type
Definition: gclientprotocol.h:51
GSmtp::ClientProtocolReply::Value
Value
Definition: gclientprotocol.h:67
G::Str::trimLeft
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
GSmtp::ClientProtocol::Sender
An interface used by ClientProtocol to send protocol messages.
Definition: gclientprotocol.h:173
GSmtp::ClientProtocolReply::is
bool is(Value v) const
Returns true if the reply value is 'v'.
Definition: gclientprotocol.cpp:711
GSmtp::ClientProtocol::doneSignal
G::Signal2< std::string, int > & doneSignal()
Returns a signal that is raised once the protocol has finished with a given message.
Definition: gclientprotocol.cpp:625
GSmtp::ClientProtocolReply::SubType
SubType
Definition: gclientprotocol.h:59
G_ASSERT
#define G_ASSERT(test)
Definition: gassert.h:30
gstr.h
GSmtp::ClientProtocol::onTimeout
virtual void onTimeout()
Final override from GNet::AbstractTimer.
Definition: gclientprotocol.cpp:485
gassert.h
GSmtp::ClientProtocolReply::ClientProtocolReply
ClientProtocolReply(const std::string &line=std::string())
Constructor for one line of text.
Definition: gclientprotocol.cpp:637
G::Str::replaceAll
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
GAuth::Secrets
A simple interface to a store of secrets as used in authentication.
Definition: gsecrets.h:44
GSmtp::ClientProtocolReply::ok
static ClientProtocolReply ok()
Factory function for an ok reply.
Definition: gclientprotocol.cpp:660
GSmtp::ClientProtocolReply::textLine
std::string textLine(const std::string &prefix) const
Returns a line of text() which starts with prefix.
Definition: gclientprotocol.cpp:727
GSmtp::ClientProtocol::sendDone
void sendDone()
To be called when a blocked connection becomes unblocked.
Definition: gclientprotocol.cpp:106
GSmtp::ClientProtocol::start
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.
Definition: gclientprotocol.cpp:60
G_LOG
#define G_LOG(expr)
Definition: glog.h:98
gfile.h
GSmtp::ClientProtocolReply::value
int value() const
Returns the numeric value of the reply.
Definition: gclientprotocol.cpp:706
G::Signal0
Part of the slot/signal system.
Definition: gslot.h:138
G::Str::readLineFrom
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
G_DEBUG
#define G_DEBUG(expr)
Definition: glog.h:95
gxtext.h
GNet::SocketProtocol::sslCapable
static bool sslCapable()
Returns true if the implementation supports TLS/SSL.
Definition: gsocketprotocol.cpp:543
GSmtp::ClientProtocolReply::subType
SubType subType() const
Returns the reply sub-type.
Definition: gclientprotocol.cpp:753
glocal.h
gbase64.h
GSmtp::ClientProtocol::apply
bool apply(const std::string &rx)
Called on receipt of a line of text from the server.
Definition: gclientprotocol.cpp:146
GAuth::SaslClient
A class for implementing the client-side SASL challenge/response concept.
Definition: gsaslclient.h:48
GSmtp::ClientProtocol::Config::Config
Config(const std::string &, unsigned int, unsigned int, unsigned int, bool, bool, bool)
Definition: gclientprotocol.cpp:790
gsmtp.h
gclientprotocol.h
GSmtp::ClientProtocolReply::textContains
bool textContains(std::string s) const
Returns true if the text() contains the given substring.
Definition: gclientprotocol.cpp:774
GSmtp::ClientProtocolReply::add
bool add(const ClientProtocolReply &other)
Adds more lines to this reply.
Definition: gclientprotocol.cpp:763
G::Base64::decode
static std::string decode(const std::string &)
Decodes the given string.
Definition: gbase64.cpp:131
GSmtp::ClientProtocolReply::error
static ClientProtocolReply error(const std::string &reason)
Factory function for a generalised error reply.
Definition: gclientprotocol.cpp:682
GSmtp::ClientProtocolReply::positive
bool positive() const
Returns true if the numeric value of the reply is less that four hundred.
Definition: gclientprotocol.cpp:701
GSmtp::ClientProtocol::preprocessorSignal
G::Signal0 & preprocessorSignal()
Returns a signal that is raised when the protocol needs to do message preprocessing.
Definition: gclientprotocol.cpp:630
gmemory.h
GSmtp::ClientProtocol::Sender::~Sender
virtual ~Sender()
Definition: gclientprotocol.cpp:784
G::Str::join
static std::string join(const Strings &strings, const std::string &sep)
Concatenates a set of strings.
Definition: gstr.cpp:799
G::Base64::encode
static std::string encode(const std::string &s, const std::string &line_break)
Encodes the given string.
Definition: gbase64.cpp:68
GSmtp::ClientProtocol::preprocessorDone
void preprocessorDone(bool ok, const std::string &reason)
To be called when the Preprocessor interface has done its thing.
Definition: gclientprotocol.cpp:81
GSmtp::ClientProtocolReply
A private implementation class used by ClientProtocol.
Definition: gclientprotocol.h:48
G_WARNING
#define G_WARNING(expr)
Definition: glog.h:107
GSmtp::ClientProtocolReply::text
std::string text() const
Returns the complete text of the reply, excluding the numeric part, and with embedded newlines...
Definition: gclientprotocol.cpp:722
gresolver.h
G::Str::toUpper
static void toUpper(std::string &s)
Replaces all lowercase characters in string 's' by uppercase characters.
Definition: gstr.cpp:422