E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gclientprotocol.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2018 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 "gsmtp.h"
23 #include "glocal.h"
24 #include "gfile.h"
25 #include "gsaslclient.h"
26 #include "gbase64.h"
27 #include "gstr.h"
28 #include "gssl.h"
29 #include "gxtext.h"
30 #include "gclientprotocol.h"
31 #include "gsocketprotocol.h"
32 #include "gresolver.h"
33 #include "glog.h"
34 #include "gassert.h"
35
36 GSmtp::ClientProtocol::ClientProtocol( GNet::ExceptionHandler & eh , Sender & sender ,
37 const GAuth::Secrets & secrets , Config config , bool in_secure_tunnel ) :
38 GNet::TimerBase(eh) ,
39 m_sender(sender) ,
40 m_secrets(secrets) ,
41 m_thishost(config.thishost_name) ,
42 m_state(sInit) ,
43 m_to_index(0U) ,
44 m_to_accepted(0U) ,
45 m_server_has_starttls(false) ,
46 m_server_has_auth(false) ,
47 m_server_secure(false) ,
48 m_server_has_8bitmime(false) ,
49 m_message_is_8bit(false) ,
50 m_authenticated_with_server(false) ,
51 m_must_authenticate(config.must_authenticate) ,
52 m_anonymous(config.anonymous) ,
53 m_must_accept_all_recipients(config.must_accept_all_recipients) ,
54 m_use_starttls_if_possible(config.use_starttls_if_possible) ,
55 m_must_use_tls(config.must_use_tls) ,
56 m_in_secure_tunnel(in_secure_tunnel) ,
57 m_strict(config.eight_bit_strict) ,
58 m_warned(false) ,
59 m_response_timeout(config.response_timeout) ,
60 m_ready_timeout(config.ready_timeout) ,
61 m_filter_timeout(config.filter_timeout) ,
62 m_done_signal(true)
63 {
64 m_sasl.reset( new GAuth::SaslClient(m_secrets) ) ;
65 }
66
67 void GSmtp::ClientProtocol::start( const std::string & from , const G::StringArray & to ,
68 bool eight_bit , std::string mail_from_auth , unique_ptr<std::istream> content )
69 {
70 G_DEBUG( "GSmtp::ClientProtocol::start" ) ;
71
72 // reinitialise for the new message
73 m_to = to ;
74 m_to_index = 0U ;
75 m_to_accepted = 0U ;
76 m_from = from ;
77 m_content.reset( content.release() ) ;
78 m_message_is_8bit = eight_bit ;
79 m_message_mail_from_auth = mail_from_auth ; // MAIL..AUTH=
80 m_reply = Reply() ;
81
82 // (re)start the protocol
83 m_done_signal.reset() ;
84 applyEvent( Reply() , true ) ;
85 }
86
87 void GSmtp::ClientProtocol::finish()
88 {
89 G_DEBUG( "GSmtp::ClientProtocol::finish" ) ;
90 m_response_timeout = 1U ;
91 m_state = sQuitting ;
92 send( "QUIT" ) ;
93 }
94
95 void GSmtp::ClientProtocol::secure()
96 {
97 // convert the event into a pretend smtp Reply
98 applyEvent( Reply::ok(Reply::Internal_secure) ) ;
99 }
100
101 void GSmtp::ClientProtocol::sendDone()
102 {
103 if( m_state == sData )
104 {
105 size_t n = sendLines() ;
106
107 G_LOG( "GSmtp::ClientProtocol: tx>>: [" << n << " line(s) of content]" ) ;
108 if( endOfContent() )
109 {
110 m_state = sSentDot ;
111 send( "." , true ) ;
112 }
113 }
114 }
115
116 bool GSmtp::ClientProtocol::parseReply( Reply & stored_reply , const std::string & rx , std::string & reason )
117 {
118 Reply this_reply = Reply( rx ) ;
119 if( ! this_reply.validFormat() )
120 {
121 stored_reply = Reply() ;
122 reason = "invalid reply format" ;
123 return false ;
124 }
125 else if( stored_reply.validFormat() && stored_reply.incomplete() )
126 {
127 if( ! stored_reply.add(this_reply) )
128 {
129 stored_reply = Reply() ;
130 reason = "invalid continuation line" ;
131 return false ;
132 }
133 }
134 else
135 {
136 stored_reply = this_reply ;
137 }
138 return ! stored_reply.incomplete() ;
139 }
140
141 bool GSmtp::ClientProtocol::apply( const std::string & rx )
142 {
143 G_LOG( "GSmtp::ClientProtocol: rx<<: \"" << G::Str::printable(rx) << "\"" ) ;
144
145 std::string reason ;
146 bool protocol_done = false ;
147 bool complete_reply = parseReply( m_reply , rx , reason ) ;
148 if( complete_reply )
149 {
150 protocol_done = applyEvent( m_reply ) ;
151 }
152 else
153 {
154 if( reason.length() != 0U )
155 send( "550 syntax error: " , reason ) ;
156 }
157 return protocol_done ;
158 }
159
160 void GSmtp::ClientProtocol::sendEhlo()
161 {
162 send( "EHLO " , m_thishost ) ;
163 }
164
165 void GSmtp::ClientProtocol::sendHelo()
166 {
167 send( "HELO " , m_thishost ) ;
168 }
169
170 void GSmtp::ClientProtocol::sendMail()
171 {
172 const bool dodgy = m_message_is_8bit && !m_server_has_8bitmime ;
173 if( dodgy && m_strict )
174 {
175 m_state = sMessageDone ;
176 raiseDoneSignal( 0 , "failed" , "cannot send 8-bit message to 7-bit server" ) ;
177 }
178 else
179 {
180 if( dodgy && !m_warned )
181 {
182 m_warned = true ;
183 G_WARNING( "GSmtp::ClientProtocol::sendMail: sending an eight-bit message "
184 "to a server which has not advertised the 8BITMIME extension" ) ;
185 }
186 sendMailCore() ;
187 }
188 }
189
190 void GSmtp::ClientProtocol::sendMailCore()
191 {
192 std::string mail_from_tail = m_from ;
193 mail_from_tail.append( 1U , '>' ) ;
194 if( m_server_has_8bitmime )
195 {
196 mail_from_tail.append( " BODY=8BITMIME" ) ;
197 }
198 if( m_authenticated_with_server && m_message_mail_from_auth.empty() && !m_sasl->id().empty() )
199 {
200 // default policy is to use the session authentication id, although
201 // this is not strictly conforming with RFC-2554
202 mail_from_tail.append( " AUTH=" ) ;
203 mail_from_tail.append( G::Xtext::encode(m_sasl->id()) ) ;
204 }
205 else if( m_authenticated_with_server && G::Xtext::valid(m_message_mail_from_auth) )
206 {
207 mail_from_tail.append( " AUTH=" ) ;
208 mail_from_tail.append( m_message_mail_from_auth ) ;
209 }
210 else if( m_authenticated_with_server )
211 {
212 mail_from_tail.append( " AUTH=<>" ) ;
213 }
214 send( "MAIL FROM:<" , mail_from_tail ) ;
215 }
216
217 bool GSmtp::ClientProtocol::applyEvent( const Reply & reply , bool is_start_event )
218 {
219 G_DEBUG( "GSmtp::ClientProtocol::applyEvent: " << reply.value() << ": " << G::Str::printable(reply.text()) ) ;
220
221 cancelTimer() ;
222
223 bool protocol_done = false ;
224 if( m_state == sInit && is_start_event )
225 {
226 // got start-event -- wait for 220 greeting
227 m_state = sStarted ;
228 if( m_ready_timeout != 0U )
229 startTimer( m_ready_timeout ) ;
230 }
231 else if( m_state == sInit && reply.is(Reply::ServiceReady_220) )
232 {
233 // got greeting before start-event
234 G_DEBUG( "GSmtp::ClientProtocol::applyEvent: init -> ready" ) ;
235 m_state = sServiceReady ;
236 }
237 else if( m_state == sServiceReady && is_start_event )
238 {
239 // got start-event after greeting
240 G_DEBUG( "GSmtp::ClientProtocol::applyEvent: ready -> sent-ehlo" ) ;
241 m_state = sSentEhlo ;
242 sendEhlo() ;
243 }
244 else if( m_state == sStarted && reply.is(Reply::ServiceReady_220) )
245 {
246 // got greeting after start-event
247 G_DEBUG( "GSmtp::ClientProtocol::applyEvent: start -> sent-ehlo" ) ;
248 m_state = sSentEhlo ;
249 sendEhlo() ;
250 }
251 else if( m_state == sMessageDone && is_start_event )
252 {
253 // new message within the current session, start the client filter
254 m_state = sFiltering ;
255 startFiltering() ;
256 }
257 else if( m_state == sSentEhlo && (
258 reply.is(Reply::SyntaxError_500) ||
259 reply.is(Reply::SyntaxError_501) ||
260 reply.is(Reply::NotImplemented_502) ) )
261 {
262 // server didn't like EHLO so fall back to HELO
263 if( m_must_use_tls && !m_in_secure_tunnel ) throw SmtpError( "tls is mandated but the server cannot do esmtp" ) ;
264 m_state = sSentHelo ;
265 sendHelo() ;
266 }
267 else if( ( m_state == sSentEhlo || m_state == sSentHelo || m_state == sSentTlsEhlo ) && reply.is(Reply::Ok_250) )
268 {
269 // hello accepted, start a new session
270 G_DEBUG( "GSmtp::ClientProtocol::applyEvent: ehlo/rset reply \"" << G::Str::printable(reply.text()) << "\"" ) ;
271 if( m_state == sSentEhlo || m_state == sSentTlsEhlo ) // esmtp
272 {
273 m_server_has_starttls = m_state == sSentEhlo && reply.textContains("\nSTARTTLS") ;
274 m_server_has_8bitmime = reply.textContains("\n8BITMIME");
275 m_server_has_auth = serverAuth( reply ) ;
276 m_server_auth_mechanisms = serverAuthMechanisms( reply ) ;
277 m_server_secure = m_state == sSentTlsEhlo || m_in_secure_tunnel ;
278 }
279 m_auth_mechanism = m_sasl->preferred( m_server_auth_mechanisms ) ;
280
281 if( !m_server_secure && m_must_use_tls )
282 {
283 if( !m_server_has_starttls )
284 throw SmtpError( "tls is mandated but the server cannot do starttls" ) ;
285 m_state = sStartTls ;
286 send( "STARTTLS" ) ;
287 }
288 else if( !m_server_secure && m_use_starttls_if_possible && m_server_has_starttls )
289 {
290 m_state = sStartTls ;
291 send( "STARTTLS" ) ;
292 }
293 else if( m_server_has_auth && !m_sasl->active() )
294 {
295 // continue -- the server will complain later if it considers authentication is mandatory
296 G_LOG( "GSmtp::ClientProtocol: not authenticating with the remote server since no "
297 "client authentication secret has been configured" ) ;
298 m_state = sFiltering ;
299 startFiltering() ;
300 }
301 else if( m_server_has_auth && m_sasl->active() && m_auth_mechanism.empty() )
302 {
303 SmtpError error( "cannot do authentication required by remote server: add a client secret" ) ;
304 G_WARNING( "GSmtp::ClientProtocol::applyEvent: " << error.what() ) ;
305 throw error ;
306 }
307 else if( m_server_has_auth && m_sasl->active() )
308 {
309 m_state = sAuth1 ;
310 send( "AUTH " , m_auth_mechanism ) ; // TODO include client initial response in auth command for client-first mechanisms
311 }
312 else if( !m_server_has_auth && m_sasl->active() && m_must_authenticate )
313 {
314 SmtpError error( "authentication is not supported by the remote smtp server" ) ;
315 G_WARNING( "GSmtp::ClientProtocol::applyEvent: " << error.what() ) ;
316 throw error ;
317 }
318 else
319 {
320 m_state = sFiltering ;
321 startFiltering() ;
322 }
323 }
324 else if( m_state == sStartTls && reply.is(Reply::ServiceReady_220) )
325 {
326 // greeting for new secure session -- start tls handshake
327 m_sender.protocolSend( std::string() , 0U , true ) ;
328 }
329 else if( m_state == sStartTls && reply.is(Reply::NotAvailable_454) )
330 {
331 // starttls rejected
332 throw TlsError( reply.errorText() ) ;
333 }
334 else if( m_state == sStartTls && reply.is(Reply::Internal_secure) )
335 {
336 // tls session established -- send ehlo
337 m_state = sSentTlsEhlo ;
338 sendEhlo() ;
339 }
340 else if( m_state == sAuth1 && reply.is(Reply::Challenge_334) &&
341 ( m_auth_mechanism == "PLAIN" || G::Base64::valid(reply.text()) ) )
342 {
343 // authentication challenge -- send the response
344 std::string challenge = m_auth_mechanism == "PLAIN" ? std::string() : G::Base64::decode( reply.text() ) ;
345 GAuth::SaslClient::Response rsp = m_sasl->response( m_auth_mechanism , challenge ) ;
346 if( rsp.error )
347 {
348 m_state = sAuth2 ;
349 send( "*" ) ; // ie. cancel authentication
350 }
351 else
352 {
353 m_state = rsp.final ? sAuth2 : m_state ;
354 send( G::Base64::encode(rsp.data,std::string()) , false , rsp.sensitive ) ;
355 }
356 }
357 else if( m_state == sAuth1 && !reply.positive() && m_sasl->next() )
358 {
359 // authentication failed -- try the next mechanism
360 AuthError error( *m_sasl.get() , reply ) ;
361 G_LOG( "GSmtp::ClientProtocol::applyEvent: " << error.what() << ": trying [" << G::Str::lower(m_sasl->preferred()) << "]" ) ;
362 m_auth_mechanism = m_sasl->preferred() ;
363 m_state = sAuth1 ;
364 send( "AUTH " , m_auth_mechanism ) ;
365 }
366 else if( m_state == sAuth1 && !reply.positive() && m_must_authenticate )
367 {
368 // authentication failed and mandatory and no more mechanisms
369 AuthError error( *m_sasl.get() , reply ) ;
370 G_WARNING( "GSmtp::ClientProtocol::applyEvent: " << error.what() ) ;
371 throw error ;
372 }
373 else if( m_state == sAuth1 && !reply.positive() )
374 {
375 // authentication failed, but optional -- continue
376 G_ASSERT( !m_authenticated_with_server ) ;
377 G_WARNING( "GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl.get(),reply).str() << ": continuing" ) ;
378 m_state = sFiltering ;
379 startFiltering() ;
380 }
381 else if( m_state == sAuth2 && reply.is(Reply::Challenge_334) && G::Base64::valid(reply.text()) )
382 {
383 // allow a new challenge even if expecting a final authentication response (esp. XOAUTH2)
384 GAuth::SaslClient::Response rsp = m_sasl->response( m_auth_mechanism , G::Base64::decode(reply.text()) ) ;
385 if( rsp.error )
386 send( "*" ) ; // ie. cancel authentication
387 else
388 send( G::Base64::encode(rsp.data,std::string()) , false , rsp.sensitive ) ;
389 }
390 else if( m_state == sAuth2 && !reply.positive() && m_sasl->next() )
391 {
392 // authentication failed -- try the next mechanism
393 G_LOG( "GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl.get(),reply).str() << ": trying [" << G::Str::lower(m_sasl->preferred()) << "]" ) ;
394 m_auth_mechanism = m_sasl->preferred() ;
395 m_state = sAuth1 ;
396 send( "AUTH " , m_auth_mechanism ) ;
397 }
398 else if( m_state == sAuth2 && reply.is(Reply::Authenticated_235) )
399 {
400 // authenticated -- send first message
401 m_authenticated_with_server = true ;
402 G_LOG( "GSmtp::ClientProtocol::applyEvent: successful authentication with remote server " << m_sasl->info() ) ;
403 m_state = sFiltering ;
404 startFiltering() ;
405 }
406 else if( m_state == sAuth2 && m_must_authenticate )
407 {
408 // authentication failed and mandatory and no more mechanisms
409 AuthError error( *m_sasl.get() , reply ) ;
410 G_WARNING( "GSmtp::ClientProtocol::applyEvent: " << error.what() ) ;
411 throw error ;
412 }
413 else if( m_state == sAuth2 )
414 {
415 // authentication failed, but optional -- continue
416 G_ASSERT( !m_authenticated_with_server ) ;
417 G_WARNING( "GSmtp::ClientProtocol::applyEvent: " << AuthError(*m_sasl.get(),reply).str() << "]: continuing" ) ;
418 m_state = sFiltering ;
419 startFiltering() ;
420 }
421 else if( m_state == sFiltering && reply.is(Reply::Internal_filter_ok) )
422 {
423 // filter finished with 'ok'
424 m_state = sSentMail ;
425 sendMail() ;
426 }
427 else if( m_state == sFiltering && reply.is(Reply::Internal_filter_abandon) )
428 {
429 // filter failed with 'abandon'
430 m_state = sMessageDone ;
431 raiseDoneSignal( -1 , std::string() ) ;
432 }
433 else if( m_state == sFiltering && reply.is(Reply::Internal_filter_error) )
434 {
435 // filter failed with 'error'
436 m_state = sMessageDone ;
437 raiseDoneSignal( -2 , reply.errorText() , reply.errorReason() ) ;
438 }
439 else if( m_state == sSentMail && reply.is(Reply::Ok_250) )
440 {
441 // got reponse to mail-from -- send rcpt-to
442 std::string to = m_to.at( m_to_index++ ) ;
443 m_state = sSentRcpt ;
444 send( "RCPT TO:<" , to , ">" ) ;
445 }
446 else if( m_state == sSentRcpt && m_to_index < m_to.size() )
447 {
448 // got reponse to rctp-to and more recipients to go
449 if( reply.positive() )
450 m_to_accepted++ ;
451 else
452 G_WARNING( "GSmtp::ClientProtocol: recipient rejected" ) ;
453
454 std::string to = m_to.at( m_to_index++ ) ;
455 send( "RCPT TO:<" , to , ">" ) ;
456 }
457 else if( m_state == sSentRcpt )
458 {
459 // got reponse to rctp-to and all recipients requested
460 if( reply.positive() )
461 m_to_accepted++ ;
462 else
463 G_WARNING( "GSmtp::ClientProtocol: recipient rejected" ) ;
464
465 if( ( m_must_accept_all_recipients && m_to_accepted != m_to.size() ) || m_to_accepted == 0U )
466 {
467 m_state = sSentDataStub ;
468 send( "RSET" ) ;
469 }
470 else
471 {
472 m_state = sSentData ;
473 send( "DATA" ) ;
474 }
475 }
476 else if( m_state == sSentData && reply.is(Reply::OkForData_354) )
477 {
478 // data command accepted -- send content until flow-control asserted or all sent
479 m_state = sData ;
480 size_t n = sendLines() ;
481 G_LOG( "GSmtp::ClientProtocol: tx>>: [" << n << " line(s) of content]" ) ;
482 if( endOfContent() )
483 {
484 m_state = sSentDot ;
485 send( "." , true ) ;
486 }
487 }
488 else if( m_state == sSentDataStub )
489 {
490 // got response to rst following rejection of recipients
491 m_state = sMessageDone ;
492 std::string how_many = m_must_accept_all_recipients ? std::string("one or more") : std::string("all") ;
493 raiseDoneSignal( reply.value() , how_many + " recipients rejected" ) ;
494 }
495 else if( m_state == sSentDot )
496 {
497 // got response to data eot
498 m_state = sMessageDone ;
499 raiseDoneSignal( reply.value() , reply.errorText() ) ;
500 }
501 else if( m_state == sQuitting && reply.value() == 221 )
502 {
503 // got quit response
504 protocol_done = true ;
505 }
506 else if( is_start_event )
507 {
508 // got a start-event for new message, but not in a valid state
509 throw NotReady() ;
510 }
511 else
512 {
513 G_WARNING( "GSmtp::ClientProtocol: failure in client protocol: state " << static_cast<int>(m_state)
514 << ": unexpected response [" << G::Str::printable(reply.text()) << "]" ) ;
515 throw SmtpError( "unexpected response" , reply.errorText() ) ;
516 }
517 return protocol_done ;
518 }
519
520 void GSmtp::ClientProtocol::onTimeout()
521 {
522 if( m_state == sStarted )
523 {
524 // no 220 greeting seen -- go on regardless
525 G_WARNING( "GSmtp::ClientProtocol: timeout: no greeting from remote server after " << m_ready_timeout << "s: continuing" ) ;
526 m_state = sSentEhlo ;
527 sendEhlo() ;
528 }
529 else if( m_state == sFiltering )
530 {
531 throw SmtpError( "filtering timeout after " + G::Str::fromUInt(m_filter_timeout) + "s" ) ;
532 }
533 else if( m_state == sData )
534 {
535 throw SmtpError( "flow-control timeout after " + G::Str::fromUInt(m_response_timeout) + "s" ) ;
536 }
537 else
538 {
539 throw SmtpError( "response timeout after " + G::Str::fromUInt(m_response_timeout) + "s" ) ;
540 }
541 }
542
543 bool GSmtp::ClientProtocol::serverAuth( const ClientProtocolReply & reply ) const
544 {
545 return !reply.textLine("AUTH ").empty() ;
546 }
547
548 G::StringArray GSmtp::ClientProtocol::serverAuthMechanisms( const ClientProtocolReply & reply ) const
549 {
550 G::StringArray result ;
551 std::string auth_line = reply.textLine("AUTH ") ; // trailing space to avoid "AUTH="
552 if( ! auth_line.empty() )
553 {
554 std::string tail = G::Str::tail( auth_line , auth_line.find(" ") , std::string() ) ; // after "AUTH "
555 G::Str::splitIntoTokens( tail , result , G::Str::ws() ) ; // expect space separators, but ignore CR etc
556 }
557 return result ;
558 }
559
560 void GSmtp::ClientProtocol::startFiltering()
561 {
562 G_ASSERT( m_state == sFiltering ) ;
563 if( m_filter_timeout != 0U )
564 startTimer( m_filter_timeout ) ; // cancelled in applyEvent()
565 m_filter_signal.emit() ;
566 }
567
568 void GSmtp::ClientProtocol::filterDone( bool ok , const std::string & response , const std::string & reason )
569 {
570 if( ok )
571 {
572 // apply filter response event to continue with this message
573 applyEvent( Reply::ok(Reply::Internal_filter_ok) ) ;
574 }
575 else if( response.empty() )
576 {
577 // apply filter response event to abandon this message (done-code -1)
578 applyEvent( Reply::ok(Reply::Internal_filter_abandon) ) ;
579 }
580 else
581 {
582 // apply filter response event to fail this message (done-code -2)
583 applyEvent( Reply::error(Reply::Internal_filter_error,response,reason) ) ;
584 }
585 }
586
587 void GSmtp::ClientProtocol::raiseDoneSignal( int response_code , const std::string & response , const std::string & reason )
588 {
589 if( ! response.empty() && response_code == 0 )
590 G_WARNING( "GSmtp::ClientProtocol: smtp client protocol: " << response ) ;
591
592 cancelTimer() ;
593 m_content.reset() ;
594 m_done_signal.emit( response_code , response , reason ) ;
595 }
596
597 bool GSmtp::ClientProtocol::endOfContent() const
598 {
599 return !m_content->good() ;
600 }
601
602 size_t GSmtp::ClientProtocol::sendLines()
603 {
604 cancelTimer() ; // no response expected during data transfer
605
606 // the read buffer -- capacity grows to longest line, but start with something reasonable
607 std::string line( 200U , '.' ) ;
608
609 size_t n = 0U ;
610 while( sendLine(line) )
611 n++ ;
612 return n ;
613 }
614
615 bool GSmtp::ClientProtocol::sendLine( std::string & line )
616 {
617 line.erase( 1U ) ; // leave "."
618
619 bool ok = false ;
620 std::istream & stream = *(m_content.get()) ;
621 if( stream.good() )
622 {
623 const bool pre_erase = false ;
624 G::Str::readLineFrom( stream , std::string(1U,'\n') , line , pre_erase ) ;
625 G_ASSERT( line.length() >= 1U && line.at(0U) == '.' ) ;
626
627 if( !stream.fail() )
628 {
629 // read file wrt. lf -- send with cr-lf
630 if( !line.empty() && line.at(line.size()-1U) != '\r' )
631 line.append( 1U , '\r' ) ; // moot
632 line.append( 1U , '\n' ) ;
633
634 bool all_sent = m_sender.protocolSend( line , line.at(1U) == '.' ? 0U : 1U , false ) ;
635 if( !all_sent && m_response_timeout != 0U )
636 startTimer( m_response_timeout ) ; // use response timer for when flow-control asserted
637 ok = all_sent ;
638 }
639 }
640 return ok ;
641 }
642
643 void GSmtp::ClientProtocol::send( const char * p )
644 {
645 send( std::string(p) , false , false ) ;
646 }
647
648 void GSmtp::ClientProtocol::send( const char * p , const std::string & s , const char * p2 )
649 {
650 std::string line( p ) ;
651 line.append( s ) ;
652 line.append( p2 ) ;
653 send( line , false , false ) ;
654 }
655
656 void GSmtp::ClientProtocol::send( const char * p , const std::string & s )
657 {
658 send( std::string(p) + s , false , false ) ;
659 }
660
661 bool GSmtp::ClientProtocol::send( const std::string & line , bool eot , bool sensitive )
662 {
663 if( m_response_timeout != 0U )
664 startTimer( m_response_timeout ) ;
665
666 bool dot_prefix = !eot && line.length() && line.at(0U) == '.' ;
667 if( sensitive )
668 {
669 G_LOG( "GSmtp::ClientProtocol: tx>>: [response not logged]" ) ;
670 }
671 else
672 {
673 G_LOG( "GSmtp::ClientProtocol: tx>>: \"" << (dot_prefix?".":"") << G::Str::printable(line) << "\"" ) ;
674 }
675 return m_sender.protocolSend( (dot_prefix?".":"") + line + "\r\n" , 0U , false ) ;
676 }
677
678 G::Slot::Signal3<int,std::string,std::string> & GSmtp::ClientProtocol::doneSignal()
679 {
680 return m_done_signal ;
681 }
682
683 G::Slot::Signal0 & GSmtp::ClientProtocol::filterSignal()
684 {
685 return m_filter_signal ;
686 }
687
688 // ===
689
690 GSmtp::ClientProtocolReply::ClientProtocolReply( const std::string & line ) :
691 m_complete(false) ,
692 m_valid(false)
693 {
694 if( line.length() >= 3U &&
695 is_digit(line.at(0U)) &&
696 line.at(0U) <= '5' &&
697 is_digit(line.at(1U)) &&
698 is_digit(line.at(2U)) &&
699 ( line.length() == 3U || line.at(3U) == ' ' || line.at(3U) == '-' ) )
700 {
701 m_valid = true ;
702 m_complete = line.length() == 3U || line.at(3U) == ' ' ;
703 m_value = G::Str::toInt( line.substr(0U,3U) ) ;
704 if( line.length() > 4U )
705 {
706 m_text = line.substr(4U) ;
707 G::Str::trimLeft( m_text , " \t" ) ;
708 G::Str::replaceAll( m_text , "\t" , " " ) ;
709 }
710 }
711 }
712
713 GSmtp::ClientProtocolReply GSmtp::ClientProtocolReply::ok()
714 {
715 return ClientProtocolReply( "250 OK" ) ;
716 }
717
718 GSmtp::ClientProtocolReply GSmtp::ClientProtocolReply::ok( Value v , const std::string & text )
719 {
720 ClientProtocolReply reply = ok() ;
721 reply.m_value = v ;
722 if( !text.empty() )
723 reply.m_text = "OK\n" + text ;
724 G_ASSERT( reply.positive() ) ; if( !reply.positive() ) reply.m_value = 250 ;
725 return reply ;
726 }
727
728 GSmtp::ClientProtocolReply GSmtp::ClientProtocolReply::error( Value v , const std::string & response , const std::string & reason )
729 {
730 ClientProtocolReply reply( std::string("500 ")+G::Str::printable(response) ) ;
731 reply.m_value = ( v >= 500 && v < 600 ) ? v : 500 ;
732 reply.m_reason = reason ;
733 return reply ;
734 }
735
736 bool GSmtp::ClientProtocolReply::validFormat() const
737 {
738 return m_valid ;
739 }
740
741 bool GSmtp::ClientProtocolReply::incomplete() const
742 {
743 return ! m_complete ;
744 }
745
746 bool GSmtp::ClientProtocolReply::positive() const
747 {
748 return m_valid && m_value < 400 ;
749 }
750
751 int GSmtp::ClientProtocolReply::value() const
752 {
753 return m_valid ? m_value : 0 ;
754 }
755
756 bool GSmtp::ClientProtocolReply::is( Value v ) const
757 {
758 return value() == v ;
759 }
760
761 std::string GSmtp::ClientProtocolReply::errorText() const
762 {
763 const bool positive_completion = type() == PositiveCompletion ;
764 return positive_completion ? std::string() : ( m_text.empty() ? std::string("error") : m_text ) ;
765 }
766
767 std::string GSmtp::ClientProtocolReply::errorReason() const
768 {
769 return m_reason ;
770 }
771
772 std::string GSmtp::ClientProtocolReply::text() const
773 {
774 return m_text ;
775 }
776
777 std::string GSmtp::ClientProtocolReply::textLine( const std::string & prefix ) const
778 {
779 size_t start_pos = m_text.find( std::string("\n")+prefix ) ;
780 if( start_pos == std::string::npos )
781 {
782 return std::string() ;
783 }
784 else
785 {
786 start_pos++ ;
787 size_t end_pos = m_text.find( "\n" , start_pos + prefix.length() ) ;
788 return m_text.substr( start_pos , end_pos-start_pos ) ;
789 }
790 }
791
792 bool GSmtp::ClientProtocolReply::is_digit( char c )
793 {
794 return c >= '0' && c <= '9' ;
795 }
796
797 GSmtp::ClientProtocolReply::Type GSmtp::ClientProtocolReply::type() const
798 {
799 G_ASSERT( m_valid && (m_value/100) >= 1 && (m_value/100) <= 5 ) ;
800 return static_cast<Type>( m_value / 100 ) ;
801 }
802
803 GSmtp::ClientProtocolReply::SubType GSmtp::ClientProtocolReply::subType() const
804 {
805 G_ASSERT( m_valid && m_value >= 0 ) ;
806 int n = ( m_value / 10 ) % 10 ;
807 if( n < 4 )
808 return static_cast<SubType>( n ) ;
809 else
810 return Invalid_SubType ;
811 }
812
813 bool GSmtp::ClientProtocolReply::add( const ClientProtocolReply & other )
814 {
815 G_ASSERT( other.m_valid ) ;
816 G_ASSERT( m_valid ) ;
817 G_ASSERT( !m_complete ) ;
818
819 m_complete = other.m_complete ;
820 m_text.append( std::string("\n") + other.text() ) ;
821 return value() == other.value() ;
822 }
823
824 bool GSmtp::ClientProtocolReply::textContains( std::string key ) const
825 {
826 std::string text( m_text ) ;
827 G::Str::toUpper( key ) ;
828 G::Str::toUpper( text ) ;
829 return text.find(key) != std::string::npos ;
830 }
831
832 // ===
833
834 GSmtp::ClientProtocol::Sender::~Sender()
835 {
836 }
837
838 // ===
839
840 GSmtp::ClientProtocol::Config::Config( const std::string & name_ ,
841 unsigned int response_timeout_ ,
842 unsigned int ready_timeout_ , unsigned int filter_timeout_ ,
843 bool use_starttls_if_possible_ , bool must_use_tls_ ,
844 bool must_authenticate_ , bool anonymous_ ,
845 bool must_accept_all_recipients_ , bool eight_bit_strict_ ) :
846 thishost_name(name_) ,
847 response_timeout(response_timeout_) ,
848 ready_timeout(ready_timeout_) ,
849 filter_timeout(filter_timeout_) ,
850 use_starttls_if_possible(use_starttls_if_possible_) ,
851 must_use_tls(must_use_tls_) ,
852 must_authenticate(must_authenticate_) ,
853 anonymous(anonymous_) ,
854 must_accept_all_recipients(must_accept_all_recipients_) ,
855 eight_bit_strict(eight_bit_strict_)
856 {
857 }
858
859 // ==
860
861 GSmtp::ClientProtocol::AuthError::AuthError( const GAuth::SaslClient & sasl , const GSmtp::ClientProtocolReply & reply ) :
862 SmtpError( "authentication failed " + sasl.info() + ": [" + G::Str::printable(reply.text()) + "]" )
863 {
864 }
865
866 std::string GSmtp::ClientProtocol::AuthError::str() const
867 {
868 return std::string( what() ) ;
869 }
870
871 /// \file gclientprotocol.cpp
GSmtp::ClientProtocol::secure
void secure()
To be called when the secure socket protocol has been successfully established.
Definition: gclientprotocol.cpp:95
gssl.h
An interface to an underlying TLS library.
G::Base64::valid
static bool valid(const std::string &)
Returns true if the string can be decoded without any strict errors.
Definition: gbase64.cpp:66
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:701
G::Str::tail
static std::string tail(const std::string &in, size_type pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1102
GNet
Network classes.
Definition: gaddress.h:29
GSmtp::ClientProtocolReply::type
Type type() const
Returns the reply type (category).
Definition: gclientprotocol.cpp:797
gsaslclient.h
GSmtp::ClientProtocolReply::error
static ClientProtocolReply error(Value, const std::string &response, const std::string &error_reason)
Factory function for an error reply with a specific 5xx value.
Definition: gclientprotocol.cpp:728
glog.h
GSmtp::ClientProtocolReply::incomplete
bool incomplete() const
Returns true if the reply is incomplete.
Definition: gclientprotocol.cpp:741
GSmtp::ClientProtocolReply::errorText
std::string errorText() const
Returns the text() string, plus any error reason, but with the guarantee that the returned string is ...
Definition: gclientprotocol.cpp:761
GSmtp::ClientProtocol::ClientProtocol
ClientProtocol(GNet::ExceptionHandler &, Sender &sender, const GAuth::Secrets &secrets, Config config, bool in_secure_tunnel)
Constructor.
Definition: gclientprotocol.cpp:36
G::Str::toInt
static int toInt(const std::string &s)
Converts string 's' to an int.
Definition: gstr.cpp:406
GSmtp::ClientProtocol::filterSignal
G::Slot::Signal0 & filterSignal()
Returns a signal that is raised when the protocol needs to do message filtering.
Definition: gclientprotocol.cpp:683
gsocketprotocol.h
G::Xtext::encode
static std::string encode(const std::string &)
Encodes the given string.
Definition: gxtext.cpp:86
GSmtp::ClientProtocolReply::validFormat
bool validFormat() const
Returns true if a valid format.
Definition: gclientprotocol.cpp:736
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
GSmtp::ClientProtocol::doneSignal
G::Slot::Signal3< int, std::string, std::string > & doneSignal()
Returns a signal that is raised once the protocol has finished with a given message.
Definition: gclientprotocol.cpp:678
G::Base64::decode
static std::string decode(const std::string &, bool strict=false)
Decodes the given string.
Definition: gbase64.cpp:57
GSmtp::ClientProtocol::onTimeout
virtual void onTimeout() override
Override from GNet::TimerBase.
Definition: gclientprotocol.cpp:520
G::Str::splitIntoTokens
static void splitIntoTokens(const std::string &in, StringArray &out, const std::string &ws)
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:908
std
STL namespace.
GSmtp::ClientProtocol::Config
A structure containing GSmtp::ClientProtocol configuration parameters.
Definition: gclientprotocol.h:183
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:224
GSmtp::ClientProtocol::Sender
An interface used by ClientProtocol to send protocol messages.
Definition: gclientprotocol.h:164
GNet::ExceptionHandler
An abstract interface for handling exceptions thrown out of event-loop callbacks (socket events...
Definition: gexceptionhandler.h:44
GSmtp::ClientProtocolReply::is
bool is(Value v) const
Returns true if the reply value is 'v'.
Definition: gclientprotocol.cpp:756
G::Str::lower
static std::string lower(const std::string &s)
Returns a copy of 's' in which all Latin-1 uppercase characters have been replaced by lowercase chara...
Definition: gstr.cpp:599
GSmtp::ClientProtocol::filterDone
void filterDone(bool ok, const std::string &response, const std::string &reason)
To be called when the Filter interface has done its thing.
Definition: gclientprotocol.cpp:568
G::Xtext::valid
static bool valid(const std::string &)
Returns true if a valid encoding.
Definition: gxtext.cpp:72
G::Str::fromUInt
static std::string fromUInt(unsigned int ui)
Converts unsigned int 'ui' to a string.
Definition: gstr.cpp:353
gstr.h
GSmtp::ClientProtocol::finish
void finish()
Called after the last message has been sent.
Definition: gclientprotocol.cpp:87
gassert.h
GSmtp::ClientProtocolReply::ClientProtocolReply
ClientProtocolReply(const std::string &line=std::string())
Constructor for one line of text.
Definition: gclientprotocol.cpp:690
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 occurances of sub-string 'from' with 'to'...
Definition: gstr.cpp:195
GAuth::Secrets
A simple interface for a store of secrets used in authentication.
Definition: gsecrets.h:40
G
Low-level classes.
Definition: garg.h:29
GSmtp::ClientProtocolReply::ok
static ClientProtocolReply ok()
Factory function for an ok reply.
Definition: gclientprotocol.cpp:713
GSmtp::ClientProtocolReply::textLine
std::string textLine(const std::string &prefix) const
Returns a line of text() which starts with prefix.
Definition: gclientprotocol.cpp:777
GSmtp::ClientProtocol::sendDone
void sendDone()
To be called when a blocked connection becomes unblocked.
Definition: gclientprotocol.cpp:101
GSmtp::ClientProtocolReply::errorReason
std::string errorReason() const
Returns an error reason string, as passed to error().
Definition: gclientprotocol.cpp:767
gfile.h
GSmtp::ClientProtocolReply::value
int value() const
Returns the numeric value of the reply.
Definition: gclientprotocol.cpp:751
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:733
gxtext.h
G::Slot::Signal0
A signal class for zero-parameter callbacks.
Definition: gslot.h:193
GSmtp::ClientProtocolReply::subType
SubType subType() const
Returns the reply sub-type.
Definition: gclientprotocol.cpp:803
glocal.h
gbase64.h
GSmtp::ClientProtocol::start
void start(const std::string &from, const G::StringArray &to, bool eight_bit, std::string mail_from_auth, unique_ptr< std::istream > content)
Starts transmission of the given message.
Definition: gclientprotocol.cpp:67
GSmtp::ClientProtocol::apply
bool apply(const std::string &rx)
Called on receipt of a line of text from the server.
Definition: gclientprotocol.cpp:141
GAuth::SaslClient
A class that implements the client-side SASL challenge/response concept.
Definition: gsaslclient.h:39
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:824
GSmtp::ClientProtocolReply::add
bool add(const ClientProtocolReply &other)
Adds more lines to this reply.
Definition: gclientprotocol.cpp:813
GSmtp::ClientProtocolReply::positive
bool positive() const
Returns true if the numeric value of the reply is less that four hundred.
Definition: gclientprotocol.cpp:746
G::Slot::Signal3< int, std::string, std::string >
G::Base64::encode
static std::string encode(const std::string &s, const std::string &line_break)
Encodes the given string.
Definition: gbase64.cpp:52
G::Str::ws
static std::string ws()
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1078
GSmtp::ClientProtocolReply
A private implementation class used by ClientProtocol.
Definition: gclientprotocol.h:46
GSmtp::ClientProtocolReply::text
std::string text() const
Returns the text of the reply, excluding the numeric part, and with embedded newlines.
Definition: gclientprotocol.cpp:772
gresolver.h
G::Str::toUpper
static void toUpper(std::string &s)
Replaces all Latin-1 lowercase characters in string 's' by uppercase characters.
Definition: gstr.cpp:614
GAuth::SaslClient::Response
Result structure returned from GAuth::SaslClient::response.
Definition: gsaslclient.h:42