E-MailRelay
gsmtpserverprotocol.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 "gsmtpserverprotocol.h"
23#include "gsaslserverfactory.h"
24#include "gsocketprotocol.h"
25#include "gxtext.h"
26#include "glocal.h"
27#include "gbase64.h"
28#include "gdate.h"
29#include "gtime.h"
30#include "gdatetime.h"
31#include "gscope.h"
32#include "gstr.h"
33#include "gstringfield.h"
34#include "gstringtoken.h"
35#include "glog.h"
36#include "gtest.h"
37#include "gassert.h"
38#include <string>
39#include <tuple>
40
41std::unique_ptr<GAuth::SaslServer> GSmtp::ServerProtocol::newSaslServer( const GAuth::SaslServerSecrets & secrets ,
42 const std::string & sasl_config , const std::string & challenge_hostname )
43{
44 bool with_apop = false ;
45 return GAuth::SaslServerFactory::newSaslServer( secrets , with_apop , sasl_config , challenge_hostname ) ;
46}
47
49 ProtocolMessage & pm , const GAuth::SaslServerSecrets & secrets ,
50 Text & text , const GNet::Address & peer_address , const Config & config ,
51 bool enabled ) :
52 ServerSend(&sender) ,
53 m_sender(&sender) ,
54 m_verifier(verifier) ,
55 m_text(text) ,
56 m_pm(pm) ,
57 m_sasl(newSaslServer(secrets,config.sasl_server_config,config.sasl_server_challenge_hostname)) ,
58 m_config(config) ,
59 m_apply_data(nullptr) ,
60 m_apply_more(false) ,
61 m_fsm(State::Start,State::End,State::s_Same,State::s_Any) ,
62 m_with_starttls(false) ,
63 m_peer_address(peer_address) ,
64 m_secure(false) ,
65 m_client_error_count(0U) ,
66 m_session_esmtp(false) ,
67 m_bdat_arg(0U) ,
68 m_bdat_sum(0U) ,
69 m_enabled(enabled)
70{
71 m_fsm( Event::Quit , State::s_Any , State::End , &ServerProtocol::doQuit ) ;
72 m_fsm( Event::Unknown , State::Processing , State::s_Same , &ServerProtocol::doIgnore ) ;
73 m_fsm( Event::Unknown , State::s_Any , State::s_Same , &ServerProtocol::doUnknown ) ;
74 m_fsm( Event::Rset , State::Start , State::s_Same , &ServerProtocol::doRset ) ;
75 m_fsm( Event::Rset , State::s_Any , State::Idle , &ServerProtocol::doRset ) ;
76 m_fsm( Event::Noop , State::s_Any , State::s_Same , &ServerProtocol::doNoop ) ;
77 m_fsm( Event::Help , State::s_Any , State::s_Same , &ServerProtocol::doHelp ) ;
78 m_fsm( Event::Expn , State::s_Any , State::s_Same , &ServerProtocol::doExpn ) ;
79 m_fsm( Event::Vrfy , State::Start , State::VrfyStart , &ServerProtocol::doVrfy , State::s_Same ) ;
80 m_fsm( Event::VrfyReply , State::VrfyStart , State::Start , &ServerProtocol::doVrfyReply ) ;
81 m_fsm( Event::Vrfy , State::Idle , State::VrfyIdle , &ServerProtocol::doVrfy , State::s_Same ) ;
82 m_fsm( Event::VrfyReply , State::VrfyIdle , State::Idle , &ServerProtocol::doVrfyReply ) ;
83 m_fsm( Event::Vrfy , State::GotMail , State::VrfyGotMail, &ServerProtocol::doVrfy , State::s_Same ) ;
84 m_fsm( Event::VrfyReply , State::VrfyGotMail, State::GotMail , &ServerProtocol::doVrfyReply ) ;
85 m_fsm( Event::Vrfy , State::GotRcpt , State::VrfyGotRcpt, &ServerProtocol::doVrfy , State::s_Same ) ;
86 m_fsm( Event::VrfyReply , State::VrfyGotRcpt, State::GotRcpt , &ServerProtocol::doVrfyReply ) ;
87 m_fsm( Event::Ehlo , State::s_Any , State::Idle , &ServerProtocol::doEhlo , State::s_Same ) ;
88 m_fsm( Event::Helo , State::s_Any , State::Idle , &ServerProtocol::doHelo , State::s_Same ) ;
89 m_fsm( Event::Mail , State::Idle , State::GotMail , &ServerProtocol::doMail , State::Idle ) ;
90 m_fsm( Event::Rcpt , State::GotMail , State::RcptTo1 , &ServerProtocol::doRcpt , State::s_Same ) ;
91 m_fsm( Event::RcptReply , State::RcptTo1 , State::GotRcpt , &ServerProtocol::doRcptToReply , State::GotMail ) ;
92 m_fsm( Event::Rcpt , State::GotRcpt , State::RcptTo2 , &ServerProtocol::doRcpt , State::s_Same ) ;
93 m_fsm( Event::RcptReply , State::RcptTo2 , State::GotRcpt , &ServerProtocol::doRcptToReply ) ;
94 m_fsm( Event::DataFail , State::GotMail , State::MustReset , &ServerProtocol::doBadDataCommand ) ;
95 m_fsm( Event::DataFail , State::GotRcpt , State::MustReset , &ServerProtocol::doBadDataCommand ) ;
96 m_fsm( Event::Data , State::GotMail , State::Idle , &ServerProtocol::doNoRecipients ) ;
97 m_fsm( Event::Data , State::GotRcpt , State::Data , &ServerProtocol::doData ) ;
98 m_fsm( Event::DataContent , State::Data , State::Data , &ServerProtocol::doDataContent ) ;
99 m_fsm( Event::Bdat , State::Idle , State::MustReset , &ServerProtocol::doBdatOutOfSequence ) ;
100 m_fsm( Event::Bdat , State::GotMail , State::Idle , &ServerProtocol::doNoRecipients ) ; // 1
101 m_fsm( Event::BdatLast , State::GotMail , State::Idle , &ServerProtocol::doNoRecipients ) ; // 2
102 m_fsm( Event::BdatLastZero , State::GotMail , State::Idle , &ServerProtocol::doNoRecipients ) ; // 3
103 m_fsm( Event::Bdat , State::GotRcpt , State::BdatData , &ServerProtocol::doBdatFirst , State::MustReset ) ; // 4
104 m_fsm( Event::BdatLast , State::GotRcpt , State::BdatDataLast , &ServerProtocol::doBdatFirstLast , State::MustReset ) ; // 5
105 m_fsm( Event::BdatLastZero , State::GotRcpt , State::BdatChecking , &ServerProtocol::doBdatFirstLastZero ) ; // 6
106 m_fsm( Event::BdatContent , State::BdatData , State::BdatIdle , &ServerProtocol::doBdatContent , State::BdatData ) ; // 7
107 m_fsm( Event::Bdat , State::BdatIdle , State::BdatData , &ServerProtocol::doBdatMore , State::MustReset ) ; // 8
108 m_fsm( Event::BdatLast , State::BdatIdle , State::BdatDataLast , &ServerProtocol::doBdatMoreLast , State::MustReset ) ; // 9
109 m_fsm( Event::BdatLastZero , State::BdatIdle , State::BdatChecking , &ServerProtocol::doBdatMoreLastZero ) ; // 10
110 m_fsm( Event::BdatContent , State::BdatDataLast , State::BdatChecking , &ServerProtocol::doBdatContentLast , State::BdatDataLast ) ;//11
111 m_fsm( Event::BdatCheck , State::BdatChecking , State::BdatProcessing , &ServerProtocol::doBdatCheck , State::Idle ) ; //12
112 m_fsm( Event::Done , State::BdatProcessing , State::Idle , &ServerProtocol::doBdatComplete ) ; // 13
113 m_fsm( Event::Eot , State::Data , State::Processing , &ServerProtocol::doEot , State::Idle ) ;
114 m_fsm( Event::Done , State::Processing , State::Idle , &ServerProtocol::doComplete ) ;
115 m_fsm( Event::Auth , State::Idle , State::Auth , &ServerProtocol::doAuth , State::Idle ) ;
116 m_fsm( Event::AuthData, State::Auth , State::Auth , &ServerProtocol::doAuthData , State::Idle ) ;
117 if( m_config.tls_starttls )
118 {
119 m_with_starttls = true ;
120 m_fsm( Event::StartTls , State::Idle , State::StartingTls , &ServerProtocol::doStartTls , State::Idle ) ;
121 m_fsm( Event::Secure , State::StartingTls , State::Idle , &ServerProtocol::doSecure ) ;
122 }
123 else if( m_config.tls_connection )
124 {
125 m_fsm.reset( State::StartingTls ) ;
126 m_fsm( Event::Secure , State::StartingTls , State::Start , &ServerProtocol::doSecureGreeting ) ;
127 }
128 m_verifier.doneSignal().connect( G::Slot::slot(*this,&ServerProtocol::verifyDone) ) ;
129 m_pm.processedSignal().connect( G::Slot::slot(*this,&ServerProtocol::protocolMessageProcessed) ) ;
130}
131
133{
134 m_pm.processedSignal().disconnect() ;
135 m_verifier.doneSignal().disconnect() ;
136}
137
139{
140 return m_change_signal ;
141}
142
144{
145 // return true if waiting for an asynchronous filter
146 // or verifier completion event
147 return
148 // states expecting Event::Done...
149 m_fsm.state() == State::Processing ||
150 // states expecting Event::VrfyReply...
151 m_fsm.state() == State::VrfyStart ||
152 m_fsm.state() == State::VrfyIdle ||
153 m_fsm.state() == State::VrfyGotMail ||
154 m_fsm.state() == State::VrfyGotRcpt ||
155 m_fsm.state() == State::RcptTo1 ||
156 m_fsm.state() == State::RcptTo2 ;
157}
158
159#ifndef G_LIB_SMALL
160bool GSmtp::ServerProtocol::rcptState() const
161{
162 return
163 m_fsm.state() == State::RcptTo1 ||
164 m_fsm.state() == State::RcptTo2 ;
165}
166#endif
167
168bool GSmtp::ServerProtocol::sendFlush() const
169{
170 // the return value is currently ignored by GSmtp::ServerPeer::protocolSend() ...
171
172 // always flush if no pipelining
173 if( !m_session_esmtp || !m_config.with_pipelining )
174 return true ;
175
176 // flush at the end of the input batch
177 if( !m_apply_more )
178 return true ;
179
180 // don't flush after RSET, MAIL-FROM, RCPT-TO, <EOT>, BDAT[!last]
181 // RFC-2920 (pipelining) 3.2 (2) (5) (6)
182 // RFC-3030 (chunking) 4.2
183 Event e = m_fsm.event() ;
184 bool batch =
185 e == Event::Rset ||
186 e == Event::Rcpt ||
187 e == Event::RcptReply ||
188 e == Event::Mail ||
189 e == Event::Done ||
190 e == Event::Bdat ;
191 return !batch ;
192}
193
195{
196 return
197 m_fsm.state() == State::Data ||
198 m_fsm.state() == State::BdatData ||
199 m_fsm.state() == State::BdatDataLast ;
200}
201
202#ifndef G_LIB_SMALL
204{
205 ServerSend::setSender( &sender ) ;
206 m_sender = &sender ;
207}
208#endif
209
211{
212 if( m_config.tls_connection )
213 m_sender->protocolSecure() ;
214 else
215 sendGreeting( m_text.greeting() , m_enabled ) ;
216}
217
218void GSmtp::ServerProtocol::applyEvent( Event event , EventData event_data )
219{
220 State new_state = m_fsm.apply( *this , event , event_data ) ;
221 if( new_state == State::s_Any )
222 throw Done( "protocol error" ) ;
223}
224
225void GSmtp::ServerProtocol::secure( const std::string & certificate ,
226 const std::string & protocol , const std::string & cipher )
227{
228 m_certificate = certificate ;
229 m_protocol = protocol ;
230 m_cipher = cipher ;
231
232 applyEvent( Event::Secure ) ;
233}
234
235void GSmtp::ServerProtocol::doSecure( EventData , bool & )
236{
237 G_DEBUG( "GSmtp::ServerProtocol::doSecure" ) ;
238 m_secure = true ;
239}
240
241void GSmtp::ServerProtocol::doSecureGreeting( EventData , bool & )
242{
243 m_secure = true ;
244 sendGreeting( m_text.greeting() , m_enabled ) ;
245}
246
247void GSmtp::ServerProtocol::doStartTls( EventData , bool & ok )
248{
249 if( m_secure )
250 {
251 sendOutOfSequence() ;
252 badClientEvent() ;
253 ok = false ;
254 }
255 else
256 {
257 sendReadyForTls() ;
258 }
259}
260
261bool GSmtp::ServerProtocol::apply( const ApplyArgsTuple & args )
262{
263 G_ASSERT( std::get<2>(args) == 2U || ( inDataState() && std::get<2>(args) == 0U ) ) ; // eolsize 0 or 2
264 G_DEBUG( "GSmtp::ServerProtocol::apply: apply "
265 "[" << G::Str::printable(G::sv_substr(G::string_view(std::get<0>(args),std::get<1>(args)),0U,10U))
266 << (std::get<1>(args)>10U?"...":"") << "] "
267 "state=" << static_cast<int>(m_fsm.state()) << " "
268 "more=" << std::get<5>(args) << " "
269 "busy=" << inBusyState() ) ;
270
271 // refuse if we are currently busy with asynchronous work
272 if( inBusyState() )
273 throw Busy() ;
274
275 // squirrel away the line buffer state
276 m_apply_data = &args ;
277 m_apply_more = std::get<5>( args ) ;
278 G::ScopeExit clear_on_return( [&](){ m_apply_data = nullptr ; } ) ;
279
280 // always emit a change signal
281 G::ScopeExit emit_on_return( [&](){ m_change_signal.emit() ; } ) ;
282
283 // the event data passed via the state machine is a string-view
284 // pointing at the apply()ed data -- this is converted to a
285 // string only if it is known to be a SMTP command
286 EventData event_data( std::get<0>(args) , std::get<1>(args) ) ;
287
288 // parse the command into an event enum
289 Event event = Event::Unknown ;
290 State state = m_fsm.state() ;
291 if( state == State::Data && isEndOfText(args) )
292 {
293 event = Event::Eot ;
294 }
295 else if( state == State::Data )
296 {
297 event = Event::DataContent ;
298 }
299 else if( state == State::BdatData || state == State::BdatDataLast )
300 {
301 event = Event::BdatContent ;
302 }
303 else if( state == State::Auth )
304 {
305 event = Event::AuthData ;
306 }
307 else
308 {
309 G_LOG( "GSmtp::ServerProtocol: rx<<: \"" << G::Str::printable(str(event_data)) << "\"" ) ;
310 event = commandEvent( event_data ) ;
311 }
312
313 // apply the event to the state-machine
314 State new_state = m_fsm.apply( *this , event , event_data ) ;
315 if( new_state == State::s_Any )
316 {
317 sendOutOfSequence() ;
318 badClientEvent() ;
319 }
320
321 // return false if we are now busy with asynchronous work
322 return !inBusyState() ;
323}
324
325void GSmtp::ServerProtocol::doDataContent( EventData , bool & )
326{
327 G_ASSERT( m_apply_data != nullptr ) ;
328 if( m_apply_data == nullptr ) throw Done( "protocol error" ) ;
329
330 const char * ptr = std::get<0>( *m_apply_data ) ;
331 std::size_t size = std::get<1>( *m_apply_data ) ;
332 std::size_t eolsize = std::get<2>( *m_apply_data ) ;
333
334 if( isEscaped(*m_apply_data) )
335 m_pm.addContent( ptr+1 , size+eolsize-1U ) ;
336 else
337 m_pm.addContent( ptr , size+eolsize ) ;
338
339 // ignore addContent() errors here -- use addContent(0) at the end to check
340}
341
342void GSmtp::ServerProtocol::doEot( EventData , bool & ok )
343{
344 G_LOG( "GSmtp::ServerProtocol: rx<<: [message content not logged]" ) ;
345 G_LOG( "GSmtp::ServerProtocol: rx<<: \".\"" ) ;
346
347 if( messageAddContentFailed() )
348 {
349 ok = false ;
350 clear() ;
351 sendFailed() ;
352 }
353 else if( messageAddContentTooBig() )
354 {
355 ok = false ;
356 clear() ;
357 sendTooBig() ;
358 }
359 else
360 {
361 m_pm.process( m_sasl->id() , m_peer_address.hostPartString() , m_certificate ) ;
362 }
363}
364
365void GSmtp::ServerProtocol::protocolMessageProcessed( const ProtocolMessage::ProcessedInfo & info )
366{
367 G_ASSERT( info.response.find_first_of("\r\t",0U,2U) == std::string::npos ) ;
368 G_ASSERT( info.success == info.response.empty() ) ;
369 G_ASSERT( info.response_code >= 0 ) ;
370 G_DEBUG( "GSmtp::ServerProtocol::protocolMessageProcessed: ok=" << (info.success?1:0) << " msgid=" << info.id.str()
371 << " rc=" << info.response_code << " rsp=[" << info.response << "] reason=[" << info.reason << "]" ) ;
372
373 std::string response = info.response ;
374 G::Str::replace( response , '\n' , ' ' ) ;
375 if( !info.success )
376 response
377 .append(1U,'\t')
378 .append(G::Str::fromInt((info.response_code<400||info.response_code>=600)?452:info.response_code)) ;
379
380 applyEvent( Event::Done , {response.data(),response.size()} ) ;
381 m_change_signal.emit() ;
382}
383
384void GSmtp::ServerProtocol::doComplete( EventData event_data , bool & )
385{
386 clear() ;
387 sendCompletionReply( event_data.empty() , code(event_data) , str(event_data) ) ;
388}
389
390void GSmtp::ServerProtocol::doQuit( EventData , bool & )
391{
392 clear() ;
393 sendQuitOk() ;
394 m_sender->protocolShutdown( m_config.shutdown_how_on_quit ) ;
395 throw Done() ;
396}
397
398void GSmtp::ServerProtocol::doBadDataCommand( EventData , bool & )
399{
400 sendBadDataOutOfSequence() ; // RFC-3030 p6
401 badClientEvent() ;
402}
403
404void GSmtp::ServerProtocol::doBdatOutOfSequence( EventData , bool & )
405{
406 sendOutOfSequence() ; // RFC-3030 p4 para 2
407 badClientEvent() ;
408}
409
410void GSmtp::ServerProtocol::doBdatFirst( EventData event_data , bool & ok )
411{
412 doBdatImp( event_data , ok , true , false , false ) ;
413}
414
415void GSmtp::ServerProtocol::doBdatFirstLast( EventData event_data , bool & ok )
416{
417 doBdatImp( event_data , ok , true , true , false ) ;
418}
419
420void GSmtp::ServerProtocol::doBdatFirstLastZero( EventData event_data , bool & ok )
421{
422 doBdatImp( event_data , ok , true , true , true ) ;
423}
424
425void GSmtp::ServerProtocol::doBdatMore( EventData event_data , bool & ok )
426{
427 doBdatImp( event_data , ok , false , false , false ) ;
428}
429
430void GSmtp::ServerProtocol::doBdatMoreLast( EventData event_data , bool & ok )
431{
432 doBdatImp( event_data , ok , false , true , false ) ;
433}
434
435void GSmtp::ServerProtocol::doBdatMoreLastZero( EventData event_data , bool & ok )
436{
437 doBdatImp( event_data , ok , false , true , true ) ;
438}
439
440void GSmtp::ServerProtocol::doBdatImp( G::string_view bdat_line , bool & ok , bool first , bool last , bool zero )
441{
442 G_ASSERT( !zero || last ) ;
443 if( first )
444 {
445 std::string received_line = m_text.received( m_session_peer_name , m_sasl->authenticated() ,
446 m_secure , m_protocol , m_cipher ) ;
447 if( received_line.length() )
448 m_pm.addReceived( received_line ) ;
449 }
450
451 if( last && zero )
452 {
453 if( messageAddContentFailed() )
454 {
455 ok = false ;
456 clear() ;
457 sendFailed() ;
458 }
459 else if( messageAddContentTooBig() )
460 {
461 ok = false ;
462 clear() ;
463 sendTooBig() ;
464 }
465 else
466 {
467 applyEvent( Event::BdatCheck ) ;
468 }
469 }
470 else
471 {
472 auto pair = parseBdatSize( bdat_line ) ;
473 std::size_t bdat_size = pair.first ;
474 bool bdat_ok = pair.second ;
475 if( !bdat_ok || ( bdat_size == 0U && !last ) )
476 {
477 G_DEBUG( "GSmtp::ServerProtocol::doBdatImp: bad bdat command: ok=" << bdat_ok << " size=" << bdat_size << " last=" << last ) ;
478 ok = false ;
479 sendInvalidArgument() ;
480 }
481 else
482 {
483 m_bdat_arg = bdat_size ;
484 m_bdat_sum = 0U ;
485 m_sender->protocolExpect( m_bdat_arg ) ;
486 }
487 }
488}
489
490void GSmtp::ServerProtocol::doBdatContent( EventData , bool & complete )
491{
492 G_ASSERT( m_apply_data != nullptr ) ;
493 if( m_apply_data == nullptr ) throw Done( "protocol error" ) ;
494
495 const char * ptr = std::get<0>( *m_apply_data ) ;
496 std::size_t size = std::get<1>( *m_apply_data ) ;
497 std::size_t eolsize = std::get<2>( *m_apply_data ) ;
498
499 G_ASSERT( eolsize == 0U ) ; // GNet::LineBuffer::expect()
500 G_ASSERT( (m_bdat_sum+size+eolsize) <= m_bdat_arg ) ;
501
502 std::size_t fullsize = size + eolsize ;
503 m_bdat_sum += fullsize ;
504 complete = m_bdat_sum >= m_bdat_arg ;
505
506 G_DEBUG( "GSmtp::ServerProtocol: rx<<: [" << fullsize << " bytes (" << m_bdat_sum << "/" << m_bdat_arg << ")]" ) ;
507 m_pm.addContent( ptr , fullsize ) ;
508
509 if( complete )
510 {
511 std::ostringstream ss ;
512 ss << m_bdat_sum << " bytes received" ;
513 sendOk( ss.str() ) ;
514 }
515}
516
517void GSmtp::ServerProtocol::doBdatContentLast( EventData , bool & complete )
518{
519 G_ASSERT( m_apply_data != nullptr ) ;
520 if( m_apply_data == nullptr ) throw Done( "protocol error" ) ;
521
522 const char * ptr = std::get<0>( *m_apply_data ) ;
523 std::size_t size = std::get<1>( *m_apply_data ) ;
524 std::size_t eolsize = std::get<2>( *m_apply_data ) ;
525
526 G_ASSERT( (m_bdat_sum+size+eolsize) <= m_bdat_arg ) ;
527
528 std::size_t fullsize = size + eolsize ;
529 m_bdat_sum += fullsize ;
530 complete = m_bdat_sum >= m_bdat_arg ;
531
532 G_DEBUG( "GSmtp::ServerProtocol: rx<<: [" << fullsize << " bytes (" << m_bdat_sum << "/" << m_bdat_arg << ")]" ) ;
533 m_pm.addContent( ptr , fullsize ) ;
534
535 if( complete )
536 {
537 applyEvent( Event::BdatCheck ) ;
538 }
539}
540
541void GSmtp::ServerProtocol::doBdatCheck( EventData , bool & ok )
542{
543 if( messageAddContentFailed() )
544 {
545 ok = false ;
546 clear() ;
547 sendFailed() ;
548 }
549 else if( messageAddContentTooBig() )
550 {
551 ok = false ;
552 clear() ;
553 sendTooBig() ;
554 }
555 else
556 {
557 m_pm.process( m_sasl->id() , m_peer_address.hostPartString() , m_certificate ) ;
558 }
559}
560
561bool GSmtp::ServerProtocol::messageAddContentFailed()
562{
563 bool failed = m_pm.addContent( nullptr , 0U ) == GStore::NewMessage::Status::Error ;
564 if( failed )
565 G_WARNING( "GSmtp::ServerProtocol::messageAddContentFailed: failed to save message content" ) ;
566 return failed ;
567}
568
569bool GSmtp::ServerProtocol::messageAddContentTooBig()
570{
571 bool too_big = m_pm.addContent( nullptr , 0U ) == GStore::NewMessage::Status::TooBig ;
572 if( too_big )
573 G_WARNING( "GSmtp::ServerProtocol::messageAddContentTooBig: message content too big" ) ;
574 return too_big ;
575}
576
577void GSmtp::ServerProtocol::doBdatComplete( EventData event_data , bool & )
578{
579 clear() ;
580 sendCompletionReply( event_data.empty() , code(event_data) , str(event_data) ) ;
581}
582
583void GSmtp::ServerProtocol::doIgnore( EventData , bool & )
584{
585}
586
587void GSmtp::ServerProtocol::doNoop( EventData , bool & )
588{
589 sendOk( "noop" ) ;
590}
591
592#ifndef G_LIB_SMALL
593void GSmtp::ServerProtocol::doNothing( EventData , bool & )
594{
595}
596#endif
597
598void GSmtp::ServerProtocol::doExpn( EventData , bool & )
599{
600 sendNotImplemented() ;
601}
602
603void GSmtp::ServerProtocol::doHelp( EventData , bool & )
604{
605 sendNotImplemented() ;
606}
607
608void GSmtp::ServerProtocol::doVrfy( EventData event_data , bool & predicate )
609{
610 if( !m_config.with_vrfy )
611 {
612 predicate = false ;
613 sendCannotVerify() ;
614 }
615 else if( m_config.mail_requires_authentication &&
616 !m_sasl->authenticated() &&
617 !m_sasl->trusted(m_peer_address.wildcards(),m_peer_address.hostPartString()) )
618 {
619 predicate = false ;
620 sendAuthRequired( m_config.mail_requires_encryption && !m_secure && m_with_starttls ) ;
621 }
622 else if( m_config.mail_requires_encryption && !m_secure )
623 {
624 predicate = false ;
625 sendEncryptionRequired( m_with_starttls ) ;
626 }
627 else
628 {
629 std::string to = parseVrfy( str(event_data) ) ;
630 if( to.empty() )
631 {
632 predicate = false ;
633 sendNotVerified( "invalid mailbox" , false ) ;
634 }
635 else
636 {
637 verify( Verifier::Command::VRFY , to ) ;
638 }
639 }
640}
641
642void GSmtp::ServerProtocol::verify( Verifier::Command command , const std::string & to , const std::string & from )
643{
644 Verifier::Info info ;
645 info.client_ip = m_peer_address ;
646 info.mail_from_parameter = from ;
647 info.auth_mechanism = m_sasl->authenticated() ? m_sasl->mechanism() : std::string("NONE") ;
648 info.auth_extra = m_sasl->id() ;
649 m_verifier.verify( command , to , info ) ;
650}
651
652void GSmtp::ServerProtocol::verifyDone( Verifier::Command command , const VerifierStatus & status )
653{
654 G_DEBUG( "GSmtp::ServerProtocol::verifyDone: verify done: [" << status.str() << "]" ) ;
655 if( status.abort )
656 throw Done( "address verifier abort" ) ;
657
658 Event event = command == Verifier::Command::RCPT ? Event::RcptReply : Event::VrfyReply ;
659
660 // pass the verification result through the state machine as a single string
661 std::string status_str = status.str() ;
662 State new_state = m_fsm.apply( *this , event , {status_str.data(),status_str.size()} ) ;
663 if( new_state == State::s_Any )
664 throw Done( "protocol error" ) ;
665
666 m_change_signal.emit() ;
667}
668
669void GSmtp::ServerProtocol::doVrfyReply( EventData event_data , bool & )
670{
671 VerifierStatus status = VerifierStatus::parse( str(event_data) ) ;
672
673 if( status.is_valid && status.is_local )
674 sendVerified( status.full_name ) ; // 250
675 else if( status.is_valid )
676 sendWillAccept( status.recipient ) ; // 252
677 else
678 sendNotVerified( status.response , status.temporary ) ; // 550 or 450
679}
680
681void GSmtp::ServerProtocol::doEhlo( EventData event_data , bool & predicate )
682{
683 std::string smtp_peer_name = parseHeloPeerName( str(event_data) ) ;
684 if( smtp_peer_name.empty() )
685 {
686 predicate = false ;
687 sendMissingParameter() ;
688 }
689 else
690 {
691 m_session_esmtp = true ;
692 m_session_peer_name = smtp_peer_name ;
693 m_sasl->reset() ;
694 clear() ;
695 G_ASSERT( !m_sasl->authenticated() ) ;
696
697 ServerSend::Advertise advertise ;
698 advertise.hello = m_text.hello( m_session_peer_name ) ;
699 advertise.max_size = m_config.max_size ; // see also NewFile::ctor
700 advertise.mechanisms = mechanisms() ;
701 advertise.starttls = m_with_starttls && !m_secure ;
702 advertise.vrfy = m_config.with_vrfy ;
703 advertise.chunking = advertise.binarymime = m_config.with_chunking ;
704 advertise.pipelining = m_config.with_pipelining ;
705 advertise.smtputf8 = m_config.with_smtputf8 ;
706 sendEhloReply( advertise ) ;
707 }
708}
709
710void GSmtp::ServerProtocol::doHelo( EventData event_data , bool & predicate )
711{
712 std::string smtp_peer_name = parseHeloPeerName( str(event_data) ) ;
713 if( smtp_peer_name.empty() )
714 {
715 predicate = false ;
716 sendMissingParameter() ;
717 }
718 else
719 {
720 m_session_peer_name = smtp_peer_name ;
721 clear() ;
722 sendHeloReply() ;
723 }
724}
725
726void GSmtp::ServerProtocol::doAuth( EventData event_data , bool & predicate )
727{
728 G::StringTokenView word( event_data , " \t" , 2U ) ;
729 std::string mechanism = G::Str::upper( word.next()() ) ;
730 std::string initial_response = G::sv_to_string( word.next()() ) ;
731 bool got_initial_response = !initial_response.empty() ;
732
733 G_DEBUG( "ServerProtocol::doAuth: [" << mechanism << "], [" << initial_response << "]" ) ;
734
735 if( m_sasl->authenticated() )
736 {
737 G_WARNING( "GSmtp::ServerProtocol: too many AUTH requests" ) ;
738 predicate = false ; // => idle
739 sendOutOfSequence() ; // see RFC-2554 "Restrictions"
740 badClientEvent() ;
741 }
742 else if( mechanisms().empty() && !m_secure && !mechanisms(true).empty() )
743 {
744 G_WARNING( "GSmtp::ServerProtocol: rejecting authentication attempt without encryption" ) ;
745 predicate = false ; // => idle
746 sendInsecureAuth( m_with_starttls ) ;
747 }
748 else if( mechanisms().empty() )
749 {
750 G_WARNING( "GSmtp::ServerProtocol: client protocol error: AUTH requested but not advertised" ) ;
751 predicate = false ;
752 sendNotImplemented() ;
753 }
754 else if( !m_sasl->init(m_secure,mechanism) )
755 {
756 G_WARNING( "GSmtp::ServerProtocol: request for unsupported server AUTH mechanism: " << mechanism ) ;
757 predicate = false ; // => idle
758 sendBadMechanism( m_sasl->preferredMechanism(m_secure) ) ;
759 }
760 else if( got_initial_response && m_sasl->mustChallenge() ) // RFC-4954 4
761 {
762 G_WARNING( "GSmtp::ServerProtocol: unexpected initial-response with a server-first AUTH mechanism" ) ;
763 predicate = false ; // => idle
764 sendInvalidArgument() ;
765 }
766 else if( got_initial_response && ( initial_response != "=" && !G::Base64::valid(initial_response) ) )
767 {
768 G_WARNING( "GSmtp::ServerProtocol: invalid base64 encoding of AUTH parameter" ) ;
769 predicate = false ; // => idle
770 sendInvalidArgument() ;
771 }
772 else if( got_initial_response )
773 {
774 std::string s = initial_response == "=" ? std::string() : G::Base64::decode(initial_response) ;
775 bool done = false ;
776 std::string next_challenge = m_sasl->apply( s , done ) ;
777 if( done )
778 {
779 predicate = false ; // => idle
780 sendAuthDone( m_sasl->authenticated() ) ;
781 }
782 else
783 {
784 sendChallenge( next_challenge ) ;
785 }
786 }
787 else
788 {
789 sendChallenge( m_sasl->initialChallenge() ) ;
790 }
791}
792
793void GSmtp::ServerProtocol::doAuthData( EventData event_data , bool & predicate )
794{
795 G_LOG( "GSmtp::ServerProtocol: rx<<: [authentication response not logged]" ) ;
796 if( event_data.size() == 1U && event_data[0] == '*' )
797 {
798 predicate = false ; // => idle
799 sendAuthenticationCancelled() ;
800 }
801 else if( !G::Base64::valid(event_data) )
802 {
803 G_WARNING( "GSmtp::ServerProtocol: invalid base64 encoding of authentication response" ) ;
804 predicate = false ; // => idle
805 sendAuthDone( false ) ;
806 }
807 else
808 {
809 bool done = false ;
810 std::string next_challenge = m_sasl->apply( G::Base64::decode(event_data) , done ) ;
811 if( done )
812 {
813 predicate = false ; // => idle
814 sendAuthDone( m_sasl->authenticated() ) ;
815 }
816 else
817 {
818 sendChallenge( next_challenge ) ;
819 }
820 }
821}
822
823void GSmtp::ServerProtocol::doMail( EventData event_data , bool & predicate )
824{
825 G::string_view mail_line = event_data ;
826 m_pm.clear() ;
827 if( !m_enabled )
828 {
829 predicate = false ;
830 sendDisabled() ;
831 }
832 else if( m_config.mail_requires_authentication &&
833 !m_sasl->authenticated() &&
834 !m_sasl->trusted(m_peer_address.wildcards(),m_peer_address.hostPartString()) )
835 {
836 G_LOG( "GSmtp::ServerProtocol::doMail: server authentication enabled "
837 "but not a trusted address: " << m_peer_address.hostPartString() ) ;
838 predicate = false ;
839 sendAuthRequired( m_config.mail_requires_encryption && !m_secure && m_with_starttls ) ;
840 }
841 else if( m_config.mail_requires_encryption && !m_secure )
842 {
843 predicate = false ;
844 sendEncryptionRequired( m_with_starttls ) ;
845 }
846 else
847 {
848 auto mail_command = parseMailFrom( mail_line ) ;
849 if( !mail_command.error.empty() )
850 {
851 predicate = false ;
852 sendBadFrom( mail_command.error ) ;
853 }
854 else if( m_config.max_size && mail_command.size > m_config.max_size )
855 {
856 // RFC-1427 6.1 (2)
857 predicate = false ;
858 sendTooBig() ;
859 }
860 else if( mail_command.utf8address && !mail_command.smtputf8 && m_config.smtputf8_strict )
861 {
862 predicate = false ;
863 sendBadFrom( "invalid character in mailbox name" ) ;
864 }
865 else
866 {
867 sendMailReply( mail_command.address ) ;
868 ProtocolMessage::FromInfo from_info ;
869 from_info.auth = mail_command.auth ;
870 from_info.body = mail_command.body ;
871 from_info.smtputf8 = mail_command.smtputf8 ;
872 from_info.utf8address = mail_command.utf8address ;
873 m_pm.setFrom( mail_command.address , from_info ) ;
874 }
875 }
876}
877
878void GSmtp::ServerProtocol::doRcpt( EventData event_data , bool & predicate )
879{
880 G::string_view rcpt_line = event_data ;
881 auto rcpt_command = parseRcptTo( rcpt_line ) ;
882 if( !rcpt_command.error.empty() )
883 {
884 predicate = false ;
885 sendBadTo( std::string() , rcpt_command.error , false ) ;
886 }
887 else if( rcpt_command.utf8address && !m_pm.fromInfo().smtputf8 && m_config.smtputf8_strict )
888 {
889 predicate = false ;
890 sendBadTo( std::string() , "invalid character in mailbox name" , false ) ;
891 }
892 else
893 {
894 verify( Verifier::Command::RCPT , rcpt_command.address , m_pm.from() ) ;
895 }
896}
897
898void GSmtp::ServerProtocol::doRcptToReply( EventData event_data , bool & predicate )
899{
900 // recover the VerifierStatus from the event-data string
901 VerifierStatus status = VerifierStatus::parse( str(event_data) ) ;
902
903 // store the status.address as the recipient address in the envelope
904 bool ok = m_pm.addTo( ProtocolMessage::ToInfo(status) ) ;
905 G_ASSERT( status.is_valid || !ok ) ;
906
907 // respond with reference the original recipient address
908 if( ok )
909 {
910 sendRcptReply( status.recipient , status.is_local ) ;
911 }
912 else
913 {
914 predicate = false ;
915 sendBadTo( status.recipient , status.response , status.temporary ) ;
916 }
917}
918
919void GSmtp::ServerProtocol::doUnknown( EventData event_data , bool & )
920{
921 sendUnrecognised( str(event_data) ) ;
922 badClientEvent() ;
923}
924
925void GSmtp::ServerProtocol::clear()
926{
927 // cancel the current message transaction -- ehlo/quit session
928 // unaffected -- forwarding client connection unaffected --
929 // sasl state unaffected
930 m_bdat_sum = 0U ;
931 m_bdat_arg = 0U ;
932 m_pm.clear() ;
933 m_verifier.cancel() ;
934}
935
936void GSmtp::ServerProtocol::doRset( EventData , bool & )
937{
938 clear() ;
939 m_pm.reset() ; // drop any ProtocolMessage forwarding client connection (moot)
940
941 sendRsetReply() ;
942}
943
944void GSmtp::ServerProtocol::doNoRecipients( EventData , bool & )
945{
946 sendNoRecipients() ;
947}
948
949void GSmtp::ServerProtocol::doData( EventData , bool & )
950{
951 std::string received_line = m_text.received( m_session_peer_name , m_sasl->authenticated() ,
952 m_secure , m_protocol , m_cipher ) ;
953
954 if( received_line.length() )
955 m_pm.addReceived( received_line ) ;
956
957 sendDataReply() ;
958}
959
960bool GSmtp::ServerProtocol::isEndOfText( const ApplyArgsTuple & args ) const
961{
962 std::size_t eolsize = std::get<2>( args ) ;
963 std::size_t linesize = std::get<3>( args ) ;
964 char c0 = std::get<4>( args ) ;
965 return linesize == 1U && eolsize == 2U && c0 == '.' ;
966}
967
968bool GSmtp::ServerProtocol::isEscaped( const ApplyArgsTuple & args ) const
969{
970 std::size_t size = std::get<1>( args ) ;
971 std::size_t linesize = std::get<3>( args ) ;
972 char c0 = std::get<4>( args ) ;
973 return size > 1U && size == linesize && c0 == '.' ;
974}
975
976GSmtp::ServerProtocol::Event GSmtp::ServerProtocol::commandEvent( G::string_view line ) const
977{
978 G::StringTokenView t( line , " \t"_sv ) ;
979 G::string_view word = t() ;
980 if( G::Str::imatch(word,"QUIT"_sv) ) return Event::Quit ;
981 if( G::Str::imatch(word,"HELO"_sv) ) return Event::Helo ;
982 if( G::Str::imatch(word,"EHLO"_sv) ) return Event::Ehlo ;
983 if( G::Str::imatch(word,"RSET"_sv) ) return Event::Rset ;
984 if( G::Str::imatch(word,"DATA"_sv) ) return dataEvent(line) ;
985 if( G::Str::imatch(word,"RCPT"_sv) ) return Event::Rcpt ;
986 if( G::Str::imatch(word,"MAIL"_sv) ) return Event::Mail ;
987 if( G::Str::imatch(word,"VRFY"_sv) ) return Event::Vrfy ;
988 if( G::Str::imatch(word,"NOOP"_sv) ) return Event::Noop ;
989 if( G::Str::imatch(word,"EXPN"_sv) ) return Event::Expn ;
990 if( G::Str::imatch(word,"HELP"_sv) ) return Event::Help ;
991 if( G::Str::imatch(word,"STARTTLS"_sv) && m_with_starttls ) return Event::StartTls ;
992 if( G::Str::imatch(word,"AUTH"_sv) ) return Event::Auth ;
993 if( G::Str::imatch(word,"BDAT"_sv) && m_config.with_chunking ) return bdatEvent(line) ;
994 return Event::Unknown ;
995}
996
997GSmtp::ServerProtocol::Event GSmtp::ServerProtocol::dataEvent( G::string_view ) const
998{
999 // RFC-3030 p6 ("BINARYMIME cannot be used with the DATA command...")
1000 if( G::Str::imatch( m_pm.bodyType() , "BINARYMIME" ) )
1001 return Event::DataFail ; // State::MustReset
1002
1003 return Event::Data ;
1004}
1005
1006GSmtp::ServerProtocol::Event GSmtp::ServerProtocol::bdatEvent( G::string_view line ) const
1007{
1008 bool last = parseBdatLast(line).second ? parseBdatLast(line).first : false ;
1009 std::size_t size = parseBdatSize(line).second ? parseBdatSize(line).first : 0L ;
1010 if( last && size == 0U )
1011 return Event::BdatLastZero ;
1012 else if( last )
1013 return Event::BdatLast ;
1014 else
1015 return Event::Bdat ;
1016}
1017
1018void GSmtp::ServerProtocol::badClientEvent()
1019{
1020 m_client_error_count++ ;
1021 if( m_config.client_error_limit && m_client_error_count >= m_config.client_error_limit )
1022 {
1023 std::string reason = "too many protocol errors from the client" ;
1024 G_DEBUG( "GSmtp::ServerProtocol::badClientEvent: " << reason << ": dropping the connection" ) ;
1025 throw Done( reason ) ;
1026 }
1027}
1028
1029int GSmtp::ServerProtocol::code( EventData sv )
1030{
1031 return G::Str::toInt( G::Str::tailView(sv,sv.find('\t'),"501"_sv) ) ;
1032}
1033
1034std::string GSmtp::ServerProtocol::str( EventData sv )
1035{
1036 return G::sv_to_string( G::Str::headView(sv,sv.find('\t'),sv) ) ;
1037}
1038
1039G::StringArray GSmtp::ServerProtocol::mechanisms() const
1040{
1041 return m_sasl->mechanisms( m_secure ) ;
1042}
1043
1044G::StringArray GSmtp::ServerProtocol::mechanisms( bool secure ) const
1045{
1046 return m_sasl->mechanisms( secure ) ;
1047}
1048
1049// ===
1050
1051GSmtp::ServerProtocol::Config::Config()
1052= default;
1053
static std::unique_ptr< SaslServer > newSaslServer(const SaslServerSecrets &, bool allow_pop, const std::string &config, const std::string &challenge_domain)
A factory function for a SaslServer.
An interface used by GAuth::SaslServer to obtain authentication secrets.
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:62
An interface used by the ServerProtocol class to assemble and process an incoming message.
virtual ProcessedSignal & processedSignal()=0
Returns a signal which is raised once process() has completed.
An interface used by GSmtp::ServerProtocol to provide response text strings.
G::Slot::Signal & changeSignal() noexcept
A signal that is emitted at the end of apply() or whenever the protocol state might have changed by s...
bool inBusyState() const
Returns true if in a state where the protocol is waiting for an asynchronous filter of address-verifi...
bool apply(const ApplyArgsTuple &)
Called on receipt of a complete line of text from the client, or possibly a line fragment iff this ob...
void setSender(ServerSender &)
Sets the ServerSender interface, overriding the constructor parameter.
void init()
Starts the protocol.
~ServerProtocol() override
Destructor.
void secure(const std::string &certificate, const std::string &protocol, const std::string &cipher)
To be called when the transport protocol successfully goes into secure mode.
bool inDataState() const
Returns true if currently in a data-transfer state meaning that the next apply() does not need to con...
ServerProtocol(ServerSender &, Verifier &, ProtocolMessage &, const GAuth::SaslServerSecrets &secrets, Text &text, const GNet::Address &peer_address, const Config &config, bool enabled)
Constructor.
A simple mix-in class for GSmtp::ServerProtocol that sends protocol responses via a GSmtp::ServerSend...
void setSender(ServerSender *)
Sets the sender interface pointer.
An interface used by ServerProtocol to send protocol responses.
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< Command, const VerifierStatus & > & doneSignal()=0
Returns a signal that is emit()ed when the verify() request is complete.
static std::string decode(string_view, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
Definition: gbase64.cpp:87
static bool valid(string_view, bool strict=true)
Returns true if the string is a valid base64 encoding, possibly allowing for embedded newlines,...
Definition: gbase64.cpp:92
A class that calls an exit function at the end of its scope.
Definition: gscope.h:46
State reset(State new_state)
Sets the current state. Returns the old state.
static string_view headView(string_view in, std::size_t pos, string_view default_={}) noexcept
Like head() but returning a view into the input string.
Definition: gstr.cpp:1311
static bool replace(std::string &s, string_view from, string_view to, std::size_t *pos_p=nullptr)
A string_view overload.
Definition: gstr.cpp:226
static string_view tailView(string_view in, std::size_t pos, string_view default_={}) noexcept
Like tail() but returning a view into the input string.
Definition: gstr.cpp:1340
static int toInt(string_view s)
Converts string 's' to an int.
Definition: gstr.cpp:541
static bool imatch(char, char) noexcept
Returns true if the two characters are the same, ignoring seven-bit case.
Definition: gstr.cpp:1418
static std::string fromInt(int i)
Converts int 'i' to a string.
Definition: gstr.h:598
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:916
static std::string upper(string_view)
Returns a copy of 's' in which all seven-bit lower-case characters have been replaced by upper-case c...
Definition: gstr.cpp:839
A zero-copy string token iterator where the token separators are runs of whitespace characters,...
Definition: gstringtoken.h:54
A class like c++17's std::string_view.
Definition: gstringview.h:51
bool enabled() noexcept
Returns true if pop code is built in.
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:240
Low-level classes.
Definition: garg.h:30
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
A configuration structure for GSmtp::ServerProtocol.
A slot holder, with connect() and emit() methods.
Definition: gslot.h:184