E-MailRelay

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