E-MailRelay
gsmtpserverprotocol.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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/// \file gsmtpserverprotocol.cpp
19///
20
21#include "gdef.h"
22#include "gsaslserverfactory.h"
23#include "gsocketprotocol.h"
24#include "gsmtpserverprotocol.h"
25#include "gxtext.h"
26#include "gbase64.h"
27#include "gdate.h"
28#include "gtime.h"
29#include "gdatetime.h"
30#include "gstr.h"
31#include "glog.h"
32#include "gtest.h"
33#include "gassert.h"
34#include <string>
35#include <tuple>
36
38 Verifier & verifier , ProtocolMessage & pmessage ,
39 const GAuth::SaslServerSecrets & secrets , const std::string & sasl_server_config ,
40 Text & text , const GNet::Address & peer_address , const Config & config ) :
41 m_sender(sender) ,
42 m_verifier(verifier) ,
43 m_text(text) ,
44 m_message(pmessage) ,
45 m_sasl(GAuth::SaslServerFactory::newSaslServer(secrets,sasl_server_config,false/*apop*/)) ,
46 m_config(config) ,
47 m_fsm(State::sStart,State::sEnd,State::s_Same,State::s_Any) ,
48 m_with_starttls(false) ,
49 m_peer_address(peer_address) ,
50 m_secure(false) ,
51 m_bad_client_count(0U) ,
52 m_bad_client_limit(8U) ,
53 m_session_authenticated(false)
54{
55 m_message.doneSignal().connect( G::Slot::slot(*this,&ServerProtocol::processDone) ) ;
56 m_verifier.doneSignal().connect( G::Slot::slot(*this,&ServerProtocol::verifyDone) ) ;
57
58 // (dont send anything to the peer from this ctor -- the Sender object is not fuly constructed)
59
60 m_fsm( Event::eQuit , State::sProcessing , State::s_Same , &ServerProtocol::doEagerQuit ) ;
61 m_fsm( Event::eQuit , State::s_Any , State::sEnd , &ServerProtocol::doQuit ) ;
62 m_fsm( Event::eUnknown , State::sProcessing , State::s_Same , &ServerProtocol::doIgnore ) ;
63 m_fsm( Event::eUnknown , State::s_Any , State::s_Same , &ServerProtocol::doUnknown ) ;
64 m_fsm( Event::eRset , State::sStart , State::s_Same , &ServerProtocol::doNoop ) ;
65 m_fsm( Event::eRset , State::s_Any , State::sIdle , &ServerProtocol::doRset ) ;
66 m_fsm( Event::eNoop , State::s_Any , State::s_Same , &ServerProtocol::doNoop ) ;
67 m_fsm( Event::eHelp , State::s_Any , State::s_Same , &ServerProtocol::doHelp ) ;
68 m_fsm( Event::eExpn , State::s_Any , State::s_Same , &ServerProtocol::doExpn ) ;
69 m_fsm( Event::eVrfy , State::sStart , State::sVrfyStart , &ServerProtocol::doVrfy , State::s_Same ) ;
70 m_fsm( Event::eVrfyReply , State::sVrfyStart , State::sStart , &ServerProtocol::doVrfyReply ) ;
71 m_fsm( Event::eVrfy , State::sIdle , State::sVrfyIdle , &ServerProtocol::doVrfy , State::s_Same ) ;
72 m_fsm( Event::eVrfyReply , State::sVrfyIdle , State::sIdle , &ServerProtocol::doVrfyReply ) ;
73 m_fsm( Event::eVrfy , State::sGotMail , State::sVrfyGotMail, &ServerProtocol::doVrfy , State::s_Same ) ;
74 m_fsm( Event::eVrfyReply , State::sVrfyGotMail, State::sGotMail , &ServerProtocol::doVrfyReply ) ;
75 m_fsm( Event::eVrfy , State::sGotRcpt , State::sVrfyGotRcpt, &ServerProtocol::doVrfy , State::s_Same ) ;
76 m_fsm( Event::eVrfyReply , State::sVrfyGotRcpt, State::sGotRcpt , &ServerProtocol::doVrfyReply ) ;
77 m_fsm( Event::eEhlo , State::s_Any , State::sIdle , &ServerProtocol::doEhlo , State::s_Same ) ;
78 m_fsm( Event::eHelo , State::s_Any , State::sIdle , &ServerProtocol::doHelo , State::s_Same ) ;
79 m_fsm( Event::eMail , State::sIdle , State::sGotMail , &ServerProtocol::doMail , State::sIdle ) ;
80 m_fsm( Event::eRcpt , State::sGotMail , State::sVrfyTo1 , &ServerProtocol::doRcpt , State::s_Same ) ;
81 m_fsm( Event::eVrfyReply , State::sVrfyTo1 , State::sGotRcpt , &ServerProtocol::doVrfyToReply , State::sGotMail ) ;
82 m_fsm( Event::eRcpt , State::sGotRcpt , State::sVrfyTo2 , &ServerProtocol::doRcpt , State::s_Same ) ;
83 m_fsm( Event::eVrfyReply , State::sVrfyTo2 , State::sGotRcpt , &ServerProtocol::doVrfyToReply ) ;
84 m_fsm( Event::eData , State::sGotMail , State::sIdle , &ServerProtocol::doNoRecipients ) ;
85 m_fsm( Event::eData , State::sGotRcpt , State::sData , &ServerProtocol::doData ) ;
86 m_fsm( Event::eContent , State::sData , State::sData , &ServerProtocol::doContent , State::sDiscarding ) ;
87 m_fsm( Event::eEot , State::sData , State::sProcessing , &ServerProtocol::doEot ) ;
88 m_fsm( Event::eDone , State::sProcessing , State::sIdle , &ServerProtocol::doComplete ) ;
89 m_fsm( Event::eContent , State::sDiscarding , State::sDiscarding , &ServerProtocol::doDiscard ) ;
90 m_fsm( Event::eEot , State::sDiscarding , State::sIdle , &ServerProtocol::doDiscarded ) ;
91
92 if( m_sasl->active() )
93 {
94 m_fsm( Event::eAuth , State::sIdle , State::sAuth , &ServerProtocol::doAuth , State::sIdle ) ;
95 m_fsm( Event::eAuthData, State::sAuth , State::sAuth , &ServerProtocol::doAuthData , State::sIdle ) ;
96 }
97 else
98 {
99 m_fsm( Event::eAuth , State::sIdle , State::sIdle , &ServerProtocol::doAuthInvalid ) ;
100 }
101
102 if( m_config.tls_starttls && GNet::SocketProtocol::secureAcceptCapable() )
103 {
104 m_with_starttls = true ;
105 m_fsm( Event::eStartTls , State::sIdle , State::sStartingTls , &ServerProtocol::doStartTls , State::sIdle ) ;
106 m_fsm( Event::eSecure , State::sStartingTls , State::sIdle , &ServerProtocol::doSecure ) ;
107 }
108 else if( m_config.tls_connection )
109 {
110 m_fsm.reset( State::sStartingTls ) ;
111 m_fsm( Event::eSecure , State::sStartingTls , State::sStart , &ServerProtocol::doSecureGreeting ) ;
112 }
113}
114
116{
117 if( m_config.tls_connection )
118 m_sender.protocolSend( std::string() , /*go-secure=*/ true ) ;
119 else
120 sendGreeting( m_text.greeting() ) ;
121}
122
124{
125 m_message.doneSignal().disconnect() ;
126 m_verifier.doneSignal().disconnect() ;
127}
128
129void GSmtp::ServerProtocol::secure( const std::string & certificate ,
130 const std::string & protocol , const std::string & cipher )
131{
132 m_certificate = certificate ;
133 m_protocol = protocol ;
134 m_cipher = cipher ;
135
136 State new_state = m_fsm.apply( *this , Event::eSecure , EventData("",0U) ) ;
137 if( new_state == State::s_Any )
138 throw ProtocolDone( "protocol error" ) ;
139}
140
141void GSmtp::ServerProtocol::doSecure( EventData , bool & )
142{
143 G_DEBUG( "GSmtp::ServerProtocol::doSecure" ) ;
144 m_secure = true ;
145}
146
147void GSmtp::ServerProtocol::doSecureGreeting( EventData , bool & )
148{
149 m_secure = true ;
150 sendGreeting( m_text.greeting() ) ;
151}
152
153void GSmtp::ServerProtocol::doStartTls( EventData , bool & ok )
154{
155 if( m_secure )
156 {
157 sendOutOfSequence() ;
158 ok = false ;
159 }
160 else
161 {
162 sendReadyForTls() ;
163 }
164}
165
167{
168 return
169 m_fsm.state() == State::sData ||
170 m_fsm.state() == State::sDiscarding ;
171}
172
173bool GSmtp::ServerProtocol::halfDuplexBusy( const char * , std::size_t ) const
174{
175 return halfDuplexBusy() ;
176}
177
179{
180 return
181 m_config.allow_pipelining && (
182 m_fsm.state() == State::sProcessing ||
183 m_fsm.state() == State::sVrfyStart ||
184 m_fsm.state() == State::sVrfyIdle ||
185 m_fsm.state() == State::sVrfyGotMail ||
186 m_fsm.state() == State::sVrfyGotRcpt ||
187 m_fsm.state() == State::sVrfyTo1 ||
188 m_fsm.state() == State::sVrfyTo2 ) ;
189}
190
191bool GSmtp::ServerProtocol::apply( const char * line_data , std::size_t line_data_size ,
192 std::size_t eolsize , std::size_t linesize , char c0 )
193{
194 G_ASSERT( eolsize == 2U || ( inDataState() && eolsize == 0U ) ) ;
195
196 // bundle the incoming data into a convenient structure
197 EventData event_data( line_data , line_data_size , eolsize , linesize , c0 ) ;
198
199 // parse the command into an event enum
200 Event event = Event::eUnknown ;
201 State state = m_fsm.state() ;
202 if( (state == State::sData || state == State::sDiscarding) && isEndOfText(event_data) )
203 {
204 event = Event::eEot ;
205 }
206 else if( state == State::sData || state == State::sDiscarding )
207 {
208 event = Event::eContent ;
209 }
210 else if( state == State::sAuth )
211 {
212 event = Event::eAuthData ;
213 }
214 else
215 {
216 std::string line( line_data , line_data_size ) ;
217 G_LOG( "GSmtp::ServerProtocol: rx<<: \"" << G::Str::printable(line) << "\"" ) ;
218 event = commandEvent( commandWord(line) ) ;
219 m_buffer = commandLine( line ) ;
220 event_data = EventData( m_buffer.data() , m_buffer.size() ) ;
221 }
222
223 // apply the event to the state-machine
224 State new_state = m_fsm.apply( *this , event , event_data ) ;
225 if( new_state == State::s_Any )
226 sendOutOfSequence() ;
227
228 // tell the network code to stop apply()ing us if we are now
229 // busy -- see GNet::LineBuffer::apply()
230 return !halfDuplexBusy() ;
231}
232
233void GSmtp::ServerProtocol::doContent( EventData event_data , bool & ok )
234{
235 if( isEscaped(event_data) )
236 ok = m_message.addText( event_data.ptr+1 , event_data.size+event_data.eolsize-1U ) ;
237 else
238 ok = m_message.addText( event_data.ptr , event_data.size+event_data.eolsize ) ;
239
240 // moves to discard state if not ok - discard state throws if so configured
241 if( !ok && m_config.disconnect_on_max_size )
242 sendTooBig( true ) ;
243}
244
245void GSmtp::ServerProtocol::doEot( EventData , bool & )
246{
247 G_LOG( "GSmtp::ServerProtocol: rx<<: [message content not logged]" ) ;
248 G_LOG( "GSmtp::ServerProtocol: rx<<: \".\"" ) ;
249 m_message.process( m_sasl->id() , m_peer_address.hostPartString() , m_certificate ) ;
250}
251
252void GSmtp::ServerProtocol::processDone( bool success , const MessageId & id ,
253 const std::string & response , const std::string & reason )
254{
255 GDEF_IGNORE_PARAMS( success , id , reason ) ;
256 G_DEBUG( "GSmtp::ServerProtocol::processDone: " << (success?1:0) << " " << id.str()
257 << " [" << response << "] [" << reason << "]" ) ;
258 G_ASSERT( success == response.empty() ) ;
259
260 State new_state = m_fsm.apply( *this , Event::eDone , EventData(response.data(),response.size()) ) ;
261 if( new_state == State::s_Any )
262 throw ProtocolDone( "protocol error" ) ;
263}
264
265void GSmtp::ServerProtocol::doComplete( EventData event_data , bool & )
266{
267 reset() ;
268 const bool empty = event_data.size == 0U ;
269 sendCompletionReply( empty , std::string(event_data.ptr,event_data.size) ) ;
270}
271
272void GSmtp::ServerProtocol::doEagerQuit( EventData , bool & )
273{
274 // broken client has sent "." and then immediatedly "QUIT" and we
275 // have not been saved by the half-duplex queueing -- if so
276 // configured we just ignore the quit with a warning
277 if( m_config.ignore_eager_quit )
278 G_WARNING( "GSmtp::ServerProtocol::doEagerQuit: ignoring out-of-sequence quit" ) ;
279 else
280 throw ProtocolDone( "protocol error: out-of-sequence quit" ) ;
281}
282
283void GSmtp::ServerProtocol::doQuit( EventData , bool & )
284{
285 reset() ;
286 sendQuitOk() ;
287 throw ProtocolDone() ;
288}
289
290void GSmtp::ServerProtocol::doDiscard( EventData , bool & )
291{
292 if( m_config.disconnect_on_max_size )
293 {
294 reset() ;
295 sendClosing() ;
296 throw ProtocolDone() ;
297 }
298}
299
300void GSmtp::ServerProtocol::doIgnore( EventData , bool & )
301{
302}
303
304void GSmtp::ServerProtocol::doNoop( EventData , bool & )
305{
306 sendOk() ;
307}
308
309void GSmtp::ServerProtocol::doNothing( EventData , bool & )
310{
311}
312
313void GSmtp::ServerProtocol::doDiscarded( EventData , bool & )
314{
315 reset() ;
316 sendTooBig() ;
317}
318
319void GSmtp::ServerProtocol::doExpn( EventData , bool & )
320{
321 sendNotImplemented() ;
322}
323
324void GSmtp::ServerProtocol::doHelp( EventData , bool & )
325{
326 sendNotImplemented() ;
327}
328
329void GSmtp::ServerProtocol::doVrfy( EventData event_data , bool & predicate )
330{
331 std::string line( event_data.ptr , event_data.size ) ;
332 if( m_config.with_vrfy )
333 {
334 std::string to = parseRcptParameter( line ) ;
335 if( to.empty() )
336 {
337 predicate = false ;
338 sendNotVerified( "invalid mailbox" , false ) ;
339 }
340 else
341 {
342 verify( to , "" ) ;
343 }
344 }
345 else
346 {
347 predicate = false ;
348 sendNotImplemented() ;
349 }
350}
351
352void GSmtp::ServerProtocol::verify( const std::string & to , const std::string & from )
353{
354 std::string mechanism = m_sasl->active() ? m_sasl->mechanism() : std::string() ;
355 std::string id = m_sasl->active() ? m_sasl->id() : std::string() ;
356 if( m_sasl->active() && !m_session_authenticated )
357 mechanism = "NONE" ;
358 m_verifier.verify( to , from , m_peer_address , mechanism , id ) ;
359}
360
361void GSmtp::ServerProtocol::verifyDone( const VerifierStatus & status )
362{
363 G_DEBUG( "GSmtp::ServerProtocol::verifyDone: verify done: [" << status.str() << "]" ) ;
364 if( status.abort )
365 throw ProtocolDone( "address verifier abort" ) ; // denial-of-service countermeasure
366
367 std::string status_str = status.str() ;
368 State new_state = m_fsm.apply( *this , Event::eVrfyReply , EventData(status_str.data(),status_str.size()) ) ;
369 if( new_state == State::s_Any )
370 throw ProtocolDone( "protocol error" ) ;
371}
372
373void GSmtp::ServerProtocol::doVrfyReply( EventData event_data , bool & )
374{
375 std::string line( event_data.ptr , event_data.size ) ;
376 VerifierStatus status = VerifierStatus::parse( line ) ;
377
378 if( status.is_valid && status.is_local )
379 sendVerified( status.full_name ) ; // 250
380 else if( status.is_valid )
381 sendWillAccept( status.recipient ) ; // 252
382 else
383 sendNotVerified( status.response , status.temporary ) ; // 550 or 450
384}
385
386std::string GSmtp::ServerProtocol::parseRcptParameter( const std::string & line ) const
387{
388 std::string to ;
389 std::size_t pos = line.find_first_of( " \t" ) ;
390 if( pos != std::string::npos )
391 to = line.substr(pos) ;
392
393 G::Str::trim( to , {" \t",2U} ) ;
394 return to ;
395}
396
397void GSmtp::ServerProtocol::doEhlo( EventData event_data , bool & predicate )
398{
399 std::string line( event_data.ptr , event_data.size ) ;
400 std::string smtp_peer_name = parsePeerName( line ) ;
401 if( smtp_peer_name.empty() )
402 {
403 predicate = false ;
404 sendMissingParameter() ;
405 }
406 else
407 {
408 m_session_peer_name = smtp_peer_name ;
409 m_session_authenticated = false ;
410 reset() ;
411 sendEhloReply() ;
412 }
413}
414
415void GSmtp::ServerProtocol::doHelo( EventData event_data , bool & predicate )
416{
417 std::string line( event_data.ptr , event_data.size ) ;
418 std::string smtp_peer_name = parsePeerName( line ) ;
419 if( smtp_peer_name.empty() )
420 {
421 predicate = false ;
422 sendMissingParameter() ;
423 }
424 else
425 {
426 m_session_peer_name = smtp_peer_name ;
427 reset() ;
428 sendHeloReply() ;
429 }
430}
431
432bool GSmtp::ServerProtocol::authenticationRequiresEncryption() const
433{
434 bool encryption_required_by_user = m_config.authentication_requires_encryption ;
435 bool encryption_required_by_sasl = m_sasl->active() && m_sasl->requiresEncryption() ;
436 return encryption_required_by_user || encryption_required_by_sasl ;
437}
438
439void GSmtp::ServerProtocol::doAuthInvalid( EventData , bool & )
440{
441 // (workround with "--server-auth" pointing to an empty file)
442 G_WARNING( "GSmtp::ServerProtocol: client protocol error: AUTH requested but not advertised" ) ;
443 sendNotImplemented() ;
444}
445
446void GSmtp::ServerProtocol::doAuth( EventData event_data , bool & predicate )
447{
448 G::StringArray word_array ;
449 G::Str::splitIntoTokens( std::string(event_data.ptr,event_data.size) , word_array , " \t" ) ;
450
451 std::string mechanism = word_array.size() > 1U ? G::Str::upper(word_array[1U]) : std::string() ;
452 std::string initial_response = word_array.size() > 2U ? word_array[2U] : std::string() ;
453 bool got_initial_response = word_array.size() > 2U ;
454
455 G_DEBUG( "ServerProtocol::doAuth: [" << mechanism << "], [" << initial_response << "]" ) ;
456
457 if( !m_secure && authenticationRequiresEncryption() )
458 {
459 G_WARNING( "GSmtp::ServerProtocol: rejecting authentication attempt without encryption" ) ;
460 predicate = false ; // => idle
461 sendBadMechanism() ; // since none until encryption
462 }
463 else if( m_session_authenticated )
464 {
465 G_WARNING( "GSmtp::ServerProtocol: too many AUTH requests" ) ;
466 predicate = false ; // => idle
467 sendOutOfSequence() ; // see RFC-2554 "Restrictions"
468 }
469 else if( ! m_sasl->init(mechanism) )
470 {
471 G_WARNING( "GSmtp::ServerProtocol: request for unsupported server AUTH mechanism: " << mechanism ) ;
472 predicate = false ; // => idle
473 sendBadMechanism() ;
474 }
475 else if( got_initial_response && m_sasl->mustChallenge() ) // RFC-4959 4
476 {
477 G_WARNING( "GSmtp::ServerProtocol: unexpected initial-response with a server-first AUTH mechanism" ) ;
478 predicate = false ; // => idle
479 sendInvalidArgument() ;
480 }
481 else if( got_initial_response && ( initial_response != "=" && !G::Base64::valid(initial_response) ) )
482 {
483 G_WARNING( "GSmtp::ServerProtocol: invalid base64 encoding of AUTH parameter" ) ;
484 predicate = false ; // => idle
485 sendInvalidArgument() ;
486 }
487 else if( got_initial_response )
488 {
489 std::string s = initial_response == "=" ? std::string() : G::Base64::decode(initial_response) ;
490 bool done = false ;
491 std::string next_challenge = m_sasl->apply( s , done ) ;
492 if( done )
493 {
494 predicate = false ; // => idle
495 m_session_authenticated = m_sasl->authenticated() ;
496 sendAuthDone( m_sasl->authenticated() ) ;
497 }
498 else
499 {
500 sendChallenge( next_challenge ) ;
501 }
502 }
503 else
504 {
505 sendChallenge( m_sasl->initialChallenge() ) ;
506 }
507}
508
509void GSmtp::ServerProtocol::doAuthData( EventData event_data , bool & predicate )
510{
511 G_LOG( "GSmtp::ServerProtocol: rx<<: [authentication response not logged]" ) ;
512 std::string line( event_data.ptr , event_data.size ) ;
513 if( line == "*" )
514 {
515 predicate = false ; // => idle
516 sendAuthenticationCancelled() ;
517 }
518 else if( !G::Base64::valid(line) )
519 {
520 G_WARNING( "GSmtp::ServerProtocol: invalid base64 encoding of authentication response" ) ;
521 predicate = false ; // => idle
522 sendAuthDone( false ) ;
523 }
524 else
525 {
526 bool done = false ;
527 std::string next_challenge = m_sasl->apply( G::Base64::decode(line) , done ) ;
528 if( done && G::Test::enabled("sasl-server-oauth") )
529 {
530 predicate = false ;
531 m_session_authenticated = m_sasl->authenticated() ;
532 send( "535-more info at\r\n535 http://example.com" ) ; // testing
533 }
534 else if( done )
535 {
536 predicate = false ; // => idle
537 m_session_authenticated = m_sasl->authenticated() ;
538 sendAuthDone( m_sasl->authenticated() ) ;
539 }
540 else
541 {
542 sendChallenge( next_challenge ) ;
543 }
544 }
545}
546
547void GSmtp::ServerProtocol::doMail( EventData event_data , bool & predicate )
548{
549 std::string line( event_data.ptr , event_data.size ) ;
550 if( !m_session_authenticated && m_sasl->active() && !m_sasl->trusted(m_peer_address) )
551 {
552 G_LOG( "GSmtp::ServerProtocol::doMail: server authentication enabled "
553 "but not a trusted address: " << m_peer_address.hostPartString() ) ;
554 predicate = false ;
555 sendAuthRequired() ;
556 }
557 else if( !m_secure && m_config.mail_requires_encryption )
558 {
559 predicate = false ;
560 sendEncryptionRequired() ;
561 }
562 else if( m_config.max_size && parseMailSize(line) > m_config.max_size )
563 {
564 predicate = false ;
565 sendTooBig() ;
566 }
567 else
568 {
569 m_message.clear() ;
570 std::string from_address ;
571 std::string from_error_response ;
572 std::tie(from_address,from_error_response) = parseMailFrom( line ) ;
573 bool ok = from_error_response.empty() ;
574 m_message.setFrom( from_address , parseMailAuth(line) ) ;
575 predicate = ok ;
576 if( ok )
577 {
578 sendMailReply() ;
579 }
580 else
581 {
582 sendBadFrom( from_error_response ) ;
583 }
584 }
585}
586
587void GSmtp::ServerProtocol::doRcpt( EventData event_data , bool & predicate )
588{
589 std::string to_address ;
590 std::string to_error_response ;
591 std::tie(to_address,to_error_response) = parseRcptTo( std::string(event_data.ptr,event_data.size) ) ;
592 bool ok = to_error_response.empty() ;
593 if( ok )
594 {
595 verify( to_address , m_message.from() ) ;
596 }
597 else
598 {
599 predicate = false ;
600 sendBadTo( to_error_response , false ) ;
601 }
602}
603
604void GSmtp::ServerProtocol::doVrfyToReply( EventData event_data , bool & predicate )
605{
606 VerifierStatus status = VerifierStatus::parse( std::string(event_data.ptr,event_data.size) ) ;
607
608 bool ok = m_message.addTo( status ) ;
609 if( ok )
610 {
611 sendRcptReply() ;
612 }
613 else
614 {
615 predicate = false ;
616 sendBadTo( G::Str::printable(status.response) , status.temporary ) ;
617 }
618}
619
620void GSmtp::ServerProtocol::doUnknown( EventData event_data , bool & )
621{
622 sendUnrecognised( std::string(event_data.ptr,event_data.size) ) ;
623}
624
625void GSmtp::ServerProtocol::reset()
626{
627 // cancel the current message transaction -- ehlo/quit session unaffected
628 m_message.clear() ;
629 m_verifier.cancel() ;
630}
631
632void GSmtp::ServerProtocol::doRset( EventData , bool & )
633{
634 reset() ;
635 m_message.reset() ;
636 sendRsetReply() ;
637}
638
639void GSmtp::ServerProtocol::doNoRecipients( EventData , bool & )
640{
641 sendNoRecipients() ;
642}
643
644void GSmtp::ServerProtocol::doData( EventData , bool & )
645{
646 std::string received_line = m_text.received( m_session_peer_name , m_session_authenticated ,
647 m_secure , m_protocol , m_cipher ) ;
648
649 if( received_line.length() )
650 m_message.addReceived( received_line ) ;
651
652 sendDataReply() ;
653}
654
655bool GSmtp::ServerProtocol::isEndOfText( const EventData & e ) const
656{
657 return e.linesize == 1U && e.eolsize == 2U && e.c0 == '.' ;
658}
659
660bool GSmtp::ServerProtocol::isEscaped( const EventData & e ) const
661{
662 return e.size > 1U && e.size == e.linesize && e.c0 == '.' ;
663}
664
665std::string GSmtp::ServerProtocol::commandWord( const std::string & line_in ) const
666{
667 std::string line( line_in ) ;
668 G::Str::trimLeft( line , {" \t",2U} ) ;
669
670 std::size_t pos = line.find_first_of( " \t" ) ;
671 std::string command = line.substr( 0U , pos ) ;
672
673 G::Str::toUpper( command ) ;
674 return command ;
675}
676
677std::string GSmtp::ServerProtocol::commandLine( const std::string & line_in ) const
678{
679 std::string line( line_in ) ;
680 G::Str::trimLeft( line , {" \t",2U} ) ;
681 return line ;
682}
683
684GSmtp::ServerProtocol::Event GSmtp::ServerProtocol::commandEvent( const std::string & command ) const
685{
686 if( command == "QUIT" ) return Event::eQuit ;
687 if( command == "HELO" ) return Event::eHelo ;
688 if( command == "EHLO" ) return Event::eEhlo ;
689 if( command == "RSET" ) return Event::eRset ;
690 if( command == "DATA" ) return Event::eData ;
691 if( command == "RCPT" ) return Event::eRcpt ;
692 if( command == "MAIL" ) return Event::eMail ;
693 if( command == "VRFY" ) return Event::eVrfy ;
694 if( command == "NOOP" ) return Event::eNoop ;
695 if( command == "EXPN" ) return Event::eExpn ;
696 if( command == "HELP" ) return Event::eHelp ;
697 if( command == "STARTTLS" && m_with_starttls ) return Event::eStartTls ;
698 if( command == "AUTH" ) return Event::eAuth ;
699 return Event::eUnknown ;
700}
701
702const std::string & GSmtp::ServerProtocol::crlf()
703{
704 static const std::string s( "\015\012" ) ;
705 return s ;
706}
707
708void GSmtp::ServerProtocol::sendChallenge( const std::string & challenge )
709{
710 send( "334 " + G::Base64::encode(challenge) ) ;
711}
712
713void GSmtp::ServerProtocol::sendGreeting( const std::string & text )
714{
715 send( "220 " + text ) ;
716}
717
718void GSmtp::ServerProtocol::sendReadyForTls()
719{
720 send( "220 ready to start tls" , /*go-secure=*/ true ) ;
721}
722
723void GSmtp::ServerProtocol::sendInvalidArgument()
724{
725 send( "501 invalid argument" ) ;
726}
727
728void GSmtp::ServerProtocol::sendAuthenticationCancelled()
729{
730 send( "501 authentication cancelled" ) ;
731}
732
733void GSmtp::ServerProtocol::sendBadMechanism()
734{
735 send( "504 unsupported authentication mechanism" ) ;
736}
737
738void GSmtp::ServerProtocol::sendAuthDone( bool ok )
739{
740 if( ok )
741 send( "235 authentication successful" ) ;
742 else
743 send( "535 authentication failed" ) ;
744}
745
746void GSmtp::ServerProtocol::sendOutOfSequence()
747{
748 send( "503 command out of sequence -- use RSET to resynchronise" ) ;
749 badClientEvent() ;
750}
751
752void GSmtp::ServerProtocol::sendMissingParameter()
753{
754 send( "501 parameter required" ) ;
755}
756
757void GSmtp::ServerProtocol::sendQuitOk()
758{
759 send( "221 OK" ) ;
760 m_sender.protocolShutdown() ;
761}
762
763void GSmtp::ServerProtocol::sendClosing()
764{
765 send( "221 closing connection" ) ;
766 m_sender.protocolShutdown() ;
767}
768
769void GSmtp::ServerProtocol::sendVerified( const std::string & user )
770{
771 send( "250 " + user ) ;
772}
773
774void GSmtp::ServerProtocol::sendNotVerified( const std::string & response , bool temporary )
775{
776 send( (temporary?"450":"550") + std::string(1U,' ') + response ) ;
777}
778
779void GSmtp::ServerProtocol::sendWillAccept( const std::string & user )
780{
781 send( "252 cannot verify but will accept: " + G::Str::printable(user) ) ;
782}
783
784void GSmtp::ServerProtocol::sendUnrecognised( const std::string & line_in )
785{
786 std::string line = line_in.substr( 0U , 80U ) ;
787 if( line.size() >= 80U ) line.append( " ..." ) ;
788 send( "500 command unrecognized: \"" + G::Str::printable(line) + std::string(1U,'\"') ) ;
789 badClientEvent() ;
790}
791
792void GSmtp::ServerProtocol::sendNotImplemented()
793{
794 send( "502 command not implemented" ) ;
795}
796
797void GSmtp::ServerProtocol::sendAuthRequired()
798{
799 std::string more_help = authenticationRequiresEncryption() && !m_secure ? ": use starttls" : "" ;
800 send( "530 authentication required" + more_help ) ;
801}
802
803void GSmtp::ServerProtocol::sendEncryptionRequired()
804{
805 send( "538 encryption required: use starttls" ) ; // was 530 -- 538 in RFC-2554 but deprecated in RFC-4954
806}
807
808void GSmtp::ServerProtocol::sendNoRecipients()
809{
810 send( "554 no valid recipients" ) ;
811}
812
813void GSmtp::ServerProtocol::sendTooBig( bool disconnecting )
814{
815 std::string s = "552 message exceeds fixed maximum message size" ;
816 if( disconnecting ) s.append( ", disconnecting" ) ;
817 send( s ) ;
818}
819
820void GSmtp::ServerProtocol::sendDataReply()
821{
822 send( "354 start mail input -- end with <CRLF>.<CRLF>" ) ;
823}
824
825void GSmtp::ServerProtocol::sendRsetReply()
826{
827 send( "250 state reset" ) ;
828}
829
830void GSmtp::ServerProtocol::sendMailReply()
831{
832 sendOk() ;
833}
834
835void GSmtp::ServerProtocol::sendCompletionReply( bool ok , const std::string & response )
836{
837 if( ok )
838 sendOk() ;
839 else
840 // 452=>"action not taken", or perhaps 554=>"transaction failed" (so don't try again)
841 send( "452 " + response ) ;
842}
843
844void GSmtp::ServerProtocol::sendRcptReply()
845{
846 sendOk() ;
847}
848
849void GSmtp::ServerProtocol::sendBadFrom( const std::string & response_extra )
850{
851 std::string response = "553 mailbox name not allowed" ;
852 if( ! response_extra.empty() )
853 {
854 response.append( ": " ) ;
855 response.append( response_extra ) ;
856 }
857 send( response ) ;
858}
859
860void GSmtp::ServerProtocol::sendBadTo( const std::string & text , bool temporary )
861{
862 send( (temporary?"450":"550") + std::string(text.empty()?"":" ") + text ) ;
863}
864
865void GSmtp::ServerProtocol::sendEhloReply()
866{
867 std::ostringstream ss ;
868 ss << "250-" << m_text.hello(m_session_peer_name) << crlf() ;
869
870 if( m_config.max_size != 0U )
871 ss << "250-SIZE " << m_config.max_size << crlf() ;
872
873 if( m_sasl->active() && !( authenticationRequiresEncryption() && !m_secure ) )
874 ss << "250-AUTH " << m_sasl->mechanisms(' ') << crlf() ;
875
876 if( m_with_starttls && !m_secure )
877 ss << "250-STARTTLS" << crlf() ;
878
879 if( m_config.with_vrfy )
880 ss << "250-VRFY" << crlf() ; // see RFC-2821 3.5.2
881
882 ss << "250 8BITMIME" ;
883 send( ss.str() ) ;
884}
885
886void GSmtp::ServerProtocol::sendHeloReply()
887{
888 sendOk() ;
889}
890
891void GSmtp::ServerProtocol::sendOk()
892{
893 send( "250 OK" ) ;
894}
895
896void GSmtp::ServerProtocol::send( const char * line )
897{
898 send( std::string(line) ) ;
899}
900
901void GSmtp::ServerProtocol::send( std::string line , bool go_secure )
902{
903 G_LOG( "GSmtp::ServerProtocol: tx>>: \"" << G::Str::printable(line) << "\"" ) ;
904 line.append( crlf() ) ;
905 m_sender.protocolSend( line , go_secure ) ;
906}
907
908void GSmtp::ServerProtocol::badClientEvent()
909{
910 m_bad_client_count++ ;
911 if( m_bad_client_limit && m_bad_client_count >= m_bad_client_limit )
912 {
913 std::string reason = "too many protocol errors from the client" ;
914 G_DEBUG( "GSmtp::ServerProtocol::badClientEvent: " << reason << ": dropping the connection" ) ;
915 throw ProtocolDone( reason ) ;
916 }
917}
918
919std::size_t GSmtp::ServerProtocol::parseMailSize( const std::string & line ) const
920{
921 std::string parameter = parseMailParameter( line , "SIZE=" ) ;
922 if( parameter.empty() || !G::Str::isULong(parameter) )
923 return 0U ;
924 else
925 return static_cast<std::size_t>( G::Str::toULong(parameter,G::Str::Limited()) ) ;
926}
927
928std::string GSmtp::ServerProtocol::parseMailAuth( const std::string & line ) const
929{
930 return parseMailParameter( line , "AUTH=" ) ;
931}
932
933std::string GSmtp::ServerProtocol::parseMailParameter( const std::string & line , const std::string & key ) const
934{
935 std::string result ;
936 std::size_t end = line.find( '>' ) ;
937 if( end != std::string::npos )
938 {
939 G::StringArray parameters = G::Str::splitIntoTokens( line.substr(end) , " " ) ;
940 for( const auto & parameter : parameters )
941 {
942 std::size_t pos = G::Str::upper(parameter).find( key ) ;
943 if( pos == 0U && parameter.length() > key.size() )
944 {
945 // ensure valid xtext
946 result = G::Xtext::encode( G::Xtext::decode( parameter.substr(key.size()) ) ) ;
947 break ;
948 }
949 }
950 }
951 return result ;
952}
953
954std::pair<std::string,std::string> GSmtp::ServerProtocol::parseMailFrom( const std::string & line ) const
955{
956 // eg. MAIL FROM:<me@localhost>
957 return parseAddress( line ) ;
958}
959
960std::pair<std::string,std::string> GSmtp::ServerProtocol::parseRcptTo( const std::string & line ) const
961{
962 // eg. RCPT TO:<@first.net,@second.net:you@last.net>
963 // eg. RCPT TO:<Postmaster>
964 return parseAddress( line ) ;
965}
966
967std::pair<std::string,std::string> GSmtp::ServerProtocol::parseAddress( const std::string & line ) const
968{
969 std::size_t start = line.find( '<' ) ;
970 std::size_t end = line.find( '>' ) ;
971 if( start == std::string::npos || end == std::string::npos || end < start )
972 {
973 std::string response( "missing or invalid angle brackets in mailbox name" ) ;
974 return std::make_pair(std::string(),response) ;
975 }
976
977 std::string s = line.substr( start + 1U , end - start - 1U ) ;
978 G::Str::trim( s , {" \t",2U} ) ;
979
980 // strip source route
981 if( s.length() > 0U && s.at(0U) == '@' )
982 {
983 std::size_t colon_pos = s.find( ':' ) ;
984 if( colon_pos == std::string::npos )
985 {
986 std::string response( "invalid mailbox name: no colon after leading at character" ) ;
987 return std::make_pair(std::string(),response) ;
988 }
989 s = s.substr( colon_pos + 1U ) ;
990 }
991
992 return std::make_pair(s,std::string()) ;
993}
994
995std::string GSmtp::ServerProtocol::parsePeerName( const std::string & line ) const
996{
997 std::size_t pos = line.find_first_of( " \t" ) ;
998 if( pos == std::string::npos )
999 return std::string() ;
1000
1001 std::string smtp_peer_name = line.substr( pos + 1U ) ;
1002 G::Str::trim( smtp_peer_name , {" \t",2U} ) ;
1003 return smtp_peer_name ;
1004}
1005
1006// ===
1007
1008GSmtp::ServerProtocolText::ServerProtocolText( const std::string & code_ident , const std::string & thishost ,
1009 const GNet::Address & peer_address ) :
1010 m_code_ident(code_ident) ,
1011 m_thishost(thishost) ,
1012 m_peer_address(peer_address)
1013{
1014}
1015
1016std::string GSmtp::ServerProtocolText::greeting() const
1017{
1018 return m_thishost + " -- " + m_code_ident + " -- Service ready" ;
1019}
1020
1021std::string GSmtp::ServerProtocolText::hello( const std::string & ) const
1022{
1023 return m_thishost + " says hello" ;
1024}
1025
1026std::string GSmtp::ServerProtocolText::received( const std::string & smtp_peer_name ,
1027 bool authenticated , bool secure , const std::string & protocol , const std::string & cipher ) const
1028{
1029 return receivedLine( smtp_peer_name , m_peer_address.hostPartString() , m_thishost ,
1030 authenticated , secure , protocol , cipher ) ;
1031}
1032
1033std::string GSmtp::ServerProtocolText::receivedLine( const std::string & smtp_peer_name ,
1034 const std::string & peer_address , const std::string & thishost ,
1035 bool authenticated , bool secure , const std::string & , const std::string & cipher_in )
1036{
1037 const G::SystemTime t = G::SystemTime::now() ;
1038 const G::BrokenDownTime tm = t.local() ;
1039 const std::string zone = G::DateTime::offsetString(G::DateTime::offset(t)) ;
1040 const G::Date date( tm ) ;
1041 const G::Time time( tm ) ;
1042 const std::string esmtp = std::string("ESMTP") + (secure?"S":"") + (authenticated?"A":"") ; // RFC-3848
1043 const std::string peer_name = G::Str::toPrintableAscii(
1044 G::Str::replaced(smtp_peer_name,' ','-') ) ; // typically alphanumeric with ".-:[]_"
1045 std::string cipher = secure ?
1046 G::Str::only( G::sv_to_string(G::Str::alnum())+"_",G::Str::replaced(cipher_in,'-','_')) :
1047 std::string() ;
1048
1049 // RFC-5321 4.4
1050 std::ostringstream ss ;
1051 ss
1052 << "Received: from " << peer_name
1053 << " ("
1054 << "[" << peer_address << "]"
1055 << ") by " << thishost << " with " << esmtp
1056 << (cipher.empty()?"":" tls ") << cipher // RFC-8314 4.3 7.4
1057 << " ; "
1058 << date.weekdayName(true) << ", "
1059 << date.monthday() << " "
1060 << date.monthName(true) << " "
1061 << date.yyyy() << " "
1062 << time.hhmmss(":") << " "
1063 << zone ;
1064 return ss.str() ;
1065}
1066
1067// ===
1068
1069GSmtp::ServerProtocol::Config::Config()
1070= default;
1071
1072GSmtp::ServerProtocol::Config::Config( bool with_vrfy_in , unsigned int filter_timeout_in ,
1073 std::size_t max_size_in , bool authentication_requires_encryption_in , bool mail_requires_encryption_in ,
1074 bool tls_starttls_in , bool tls_connection_in ) :
1075 with_vrfy(with_vrfy_in) ,
1076 filter_timeout(filter_timeout_in) ,
1077 max_size(max_size_in) ,
1078 authentication_requires_encryption(authentication_requires_encryption_in) ,
1079 mail_requires_encryption(mail_requires_encryption_in) ,
1080 tls_starttls(tls_starttls_in) ,
1081 tls_connection(tls_connection_in)
1082{
1083}
1084
1085// ===
1086
1087GSmtp::ServerProtocol::EventData::EventData( const char * ptr_ , std::size_t size_ ) :
1088 ptr(ptr_) ,
1089 size(size_) ,
1090 eolsize(2U) ,
1091 linesize(size_) ,
1092 c0('\0')
1093{
1094}
1095
1096GSmtp::ServerProtocol::EventData::EventData( const char * ptr_ , std::size_t size_ ,
1097 std::size_t eolsize_ , std::size_t linesize_ , char c0_ ) :
1098 ptr(ptr_) ,
1099 size(size_) ,
1100 eolsize(eolsize_) ,
1101 linesize(linesize_) ,
1102 c0(c0_)
1103{
1104}
1105
An interface used by GAuth::SaslServer to obtain authentication secrets.
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:53
static bool secureAcceptCapable()
Returns true if the implementation supports TLS/SSL and a "server" profile has been configured.
An interface used by the ServerProtocol class to assemble and process an incoming message.
virtual DoneSignal & doneSignal()=0
Returns a signal which is raised once process() has completed.
ServerProtocolText(const std::string &code_ident, const std::string &thishost, const GNet::Address &peer_address)
Constructor.
static std::string receivedLine(const std::string &smtp_peer_name_from_helo, const std::string &peer_address, const std::string &thishost, bool authenticated, bool secure, const std::string &secure_protocol, const std::string &secure_cipher)
Returns a standard "Received:" line.
An interface used by ServerProtocol to send protocol replies.
An interface used by ServerProtocol to provide response text strings.
bool apply(const char *line_data, std::size_t line_size, std::size_t eolsize, std::size_t linesize, char c0)
Called on receipt of a line of text from the remote client.
void init()
Starts the protocol.
void secure(const std::string &certificate, const std::string &protocol, const std::string &cipher)
To be called when the transport protocol goes into secure mode.
bool inDataState() const
Returns true if currently in the data-transfer state.
ServerProtocol(Sender &, Verifier &, ProtocolMessage &, const GAuth::SaslServerSecrets &secrets, const std::string &sasl_server_config, Text &text, const GNet::Address &peer_address, const Config &config)
Constructor.
bool halfDuplexBusy() const
Returns true if the protocol has received a command but not yet sent a response.
virtual ~ServerProtocol()
Destructor.
static VerifierStatus parse(const std::string &str)
Parses a str() string into a structure.
An asynchronous interface that verifies recipient 'to' addresses.
Definition: gverifier.h:43
virtual G::Slot::Signal< const VerifierStatus & > & doneSignal()=0
Returns a signal that is emit()ed when the verify() request is complete.
static std::string decode(const std::string &, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
Definition: gbase64.cpp:89
static std::string encode(const std::string &s, const std::string &line_break=std::string())
Encodes the given string, optionally inserting line-breaks to limit the line length.
Definition: gbase64.cpp:84
static bool valid(const std::string &, bool strict=true)
Returns true if the string is a valid base64 encoding, possibly allowing for embedded newlines,...
Definition: gbase64.cpp:94
An encapsulation of 'struct std::tm'.
Definition: gdatetime.h:45
static Offset offset(SystemTime)
Returns the offset in seconds between UTC and localtime as at the given system time.
Definition: gdatetime.cpp:651
static std::string offsetString(Offset offset)
Converts the given utc/localtime offset into a five-character "+/-hhmm" string.
Definition: gdatetime.cpp:669
A day-month-year date class.
Definition: gdate.h:40
std::string monthName(bool brief=false) const
Returns the month as a string (in english).
Definition: gdate.cpp:153
std::string weekdayName(bool brief=false) const
Returns an english string representation of the day of the week.
Definition: gdate.cpp:136
int monthday() const
Returns the day of the month.
Definition: gdate.cpp:106
std::string yyyy() const
Returns the year as a four-digit decimal string.
Definition: gdate.cpp:175
State reset(State new_state)
Sets the current state. Returns the old state.
static string_view alnum()
Returns a string of seven-bit alphanumeric characters, ie A-Z, a-z and 0-9.
Definition: gstr.cpp:1261
static std::string replaced(const std::string &s, char from, char to)
Returns the string 's' with all occurrences of 'from' replaced by 'to'.
Definition: gstr.cpp:311
static std::string & trimLeft(std::string &s, string_view ws, std::size_t limit=0U)
Trims the lhs of s, taking off up to 'limit' of the 'ws' characters.
Definition: gstr.cpp:335
static void splitIntoTokens(const std::string &in, StringArray &out, string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:1073
static std::string toPrintableAscii(const std::string &in, char escape='\\')
Returns a 7-bit printable representation of the given input string.
Definition: gstr.cpp:905
static bool isULong(const std::string &s)
Returns true if the string can be converted into an unsigned long without throwing an exception.
Definition: gstr.cpp:453
static std::string upper(const std::string &s)
Returns a copy of 's' in which all Latin-1 lower-case characters have been replaced by upper-case cha...
Definition: gstr.cpp:754
static std::string printable(const std::string &in, char escape='\\')
Returns a printable representation of the given input string, using chacter code ranges 0x20 to 0x7e ...
Definition: gstr.cpp:885
static std::string only(const std::string &allow_chars, const std::string &s)
Returns the 's' with all occurrences of the characters not appearing in the fist string deleted.
Definition: gstr.cpp:323
static void toUpper(std::string &s)
Replaces all Latin-1 lower-case characters in string 's' by upper-case characters.
Definition: gstr.cpp:748
static unsigned long toULong(const std::string &s, Limited)
Converts string 's' to an unsigned long.
Definition: gstr.cpp:626
static std::string & trim(std::string &s, string_view ws)
Trims both ends of s, taking off any of the 'ws' characters.
Definition: gstr.cpp:359
Represents a unix-epoch time with microsecond resolution.
Definition: gdatetime.h:125
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:260
BrokenDownTime local() const
Returns the locale-dependent local broken-down time.
Definition: gdatetime.cpp:286
static bool enabled() noexcept
Returns true if test features are enabled.
Definition: gtest.cpp:79
A simple time-of-day (hh/mm/ss) class.
Definition: gtime.h:39
std::string hhmmss(const char *sep=nullptr) const
Returns the hhmmss string.
Definition: gtime.cpp:76
static std::string decode(const std::string &)
Decodes the given string.
Definition: gxtext.cpp:117
static std::string encode(const std::string &)
Encodes the given string.
Definition: gxtext.cpp:95
SASL authentication classes.
Definition: gcram.cpp:36
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:201
Low-level classes.
Definition: galign.h:28
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
A structure containing configuration parameters for ServerProtocol.
Overload discrimiator for G::Str::toUWhatever() requesting a range-limited result.
Definition: gstr.h:53