37 m_security(security) ,
39 m_store_lock(m_store) ,
40 m_sasl_server(
GAuth::SaslServerFactory::newSaslServer(server_secrets,sasl_server_config,true)) ,
41 m_peer_address(peer_address) ,
42 m_fsm(State::sStart,State::sEnd,State::s_Same,State::s_Any) ,
46 m_sasl_server_init_apop(false)
50 m_fsm( Event::eStat , State::sActive , State::sActive , &GPop::ServerProtocol::doStat ) ;
51 m_fsm( Event::eList , State::sActive , State::sActive , &GPop::ServerProtocol::doList ) ;
52 m_fsm( Event::eRetr , State::sActive , State::sData , &GPop::ServerProtocol::doRetr , State::sActive ) ;
53 m_fsm( Event::eTop , State::sActive , State::sData , &GPop::ServerProtocol::doTop , State::sActive ) ;
54 m_fsm( Event::eDele , State::sActive , State::sActive , &GPop::ServerProtocol::doDele ) ;
55 m_fsm( Event::eNoop , State::sActive , State::sActive , &GPop::ServerProtocol::doNoop ) ;
56 m_fsm( Event::eRset , State::sActive , State::sActive , &GPop::ServerProtocol::doRset ) ;
57 m_fsm( Event::eUidl , State::sActive , State::sActive , &GPop::ServerProtocol::doUidl ) ;
58 m_fsm( Event::eSent , State::sData , State::sActive , &GPop::ServerProtocol::doNothing ) ;
59 m_fsm( Event::eUser , State::sStart , State::sStart , &GPop::ServerProtocol::doUser ) ;
60 m_fsm( Event::ePass , State::sStart , State::sActive , &GPop::ServerProtocol::doPass , State::sStart ) ;
61 m_fsm( Event::eApop , State::sStart , State::sActive , &GPop::ServerProtocol::doApop , State::sStart ) ;
62 m_fsm( Event::eQuit , State::sStart , State::sEnd , &GPop::ServerProtocol::doQuitEarly ) ;
63 m_fsm( Event::eCapa , State::sStart , State::sStart , &GPop::ServerProtocol::doCapa ) ;
64 m_fsm( Event::eCapa , State::sActive , State::sActive , &GPop::ServerProtocol::doCapa ) ;
65 if( m_security.securityEnabled() )
66 m_fsm( Event::eStls , State::sStart , State::sStart , &GPop::ServerProtocol::doStls , State::sStart ) ;
67 m_fsm( Event::eAuth , State::sStart , State::sAuth , &GPop::ServerProtocol::doAuth , State::sStart ) ;
68 m_fsm( Event::eAuthData , State::sAuth , State::sAuth , &GPop::ServerProtocol::doAuthData , State::sStart ) ;
69 m_fsm( Event::eAuthComplete , State::sAuth , State::sActive , &GPop::ServerProtocol::doAuthComplete ) ;
70 m_fsm( Event::eCapa , State::sActive , State::sActive , &GPop::ServerProtocol::doCapa ) ;
71 m_fsm( Event::eQuit , State::sActive , State::sEnd , &GPop::ServerProtocol::doQuit ) ;
79void GPop::ServerProtocol::sendInit()
81 std::string greeting = std::string() +
"+OK " + m_text.greeting() ;
82 if( m_sasl_server->active() && m_sasl_server->init(
"APOP") )
84 m_sasl_server_init_apop = true ;
85 std::string apop_challenge = m_sasl_server->initialChallenge() ;
86 if( !apop_challenge.empty() )
88 greeting.append(
" " ) ;
89 greeting.append( apop_challenge ) ;
92 sendLine( greeting ) ;
95void GPop::ServerProtocol::sendOk()
100void GPop::ServerProtocol::sendError(
const std::string & more )
105 sendLine(
"-ERR " + more ) ;
108void GPop::ServerProtocol::sendError()
116 Event
event = m_fsm.state() == State::sAuth ? Event::eAuthData : commandEvent(commandWord(line)) ;
120 if( event == Event::ePass )
121 log_text = (commandPart(line,0U)+
" [password not logged]") ;
122 if( event == Event::eAuthData || event == Event::eAuthComplete )
123 log_text =
"[authentication response not logged]" ;
124 if( event == Event::eAuth && !commandPart(line,1U).empty() )
125 log_text = commandPart(line,0U) +
" " + commandPart(line,1U) ;
126 G_LOG(
"GPop::ServerProtocol: rx<<: \"" << log_text <<
"\"" ) ;
129 State new_state = m_fsm.apply( *
this , event , line ) ;
130 const bool protocol_error = new_state == State::s_Any ;
133 G_DEBUG(
"GPop::ServerProtocol::apply: protocol error: " <<
static_cast<int>(event) <<
" " <<
static_cast<int>(m_fsm.state()) ) ;
138 if( new_state == State::sData )
142void GPop::ServerProtocol::sendContent()
145 std::string line( 200 ,
'.' ) ;
147 bool end_of_content = false ;
148 while( sendContentLine(line,end_of_content) )
151 G_LOG(
"GPop::ServerProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
155 G_LOG(
"GPop::ServerProtocol: tx>>: ." ) ;
157 m_fsm.apply( *
this , Event::eSent ,
"" ) ;
166 G_DEBUG(
"GPop::ServerProtocol::resume: flow control released" ) ;
167 if( m_fsm.state() == State::sData )
171bool GPop::ServerProtocol::sendContentLine( std::string & line ,
bool & stop )
173 G_ASSERT( m_content !=
nullptr ) ;
176 bool limited = m_in_body && m_body_limit == 0L ;
177 if( m_body_limit > 0L && m_in_body )
185 bool eof = m_content->fail() || m_content->bad() ;
186 std::size_t offset = 0U ;
190 line.append( crlf() ) ;
194 line.append( crlf() ) ;
195 offset = line.at(1U) ==
'.' ? 0U : 1U ;
199 if( !m_in_body && line.length() == (offset+2U) )
203 bool line_fully_sent = m_sender.protocolSend( line , offset ) ;
206 stop = ( limited || eof ) && line_fully_sent ;
207 const bool pause = limited || eof || !line_fully_sent ;
211int GPop::ServerProtocol::commandNumber(
const std::string & line ,
int default_ , std::size_t index )
const
213 int number = default_ ;
218 catch( G::Str::Overflow & )
221 catch( G::Str::InvalidFormat & )
227std::string GPop::ServerProtocol::commandWord(
const std::string & line )
const
232std::string GPop::ServerProtocol::commandPart(
const std::string & line , std::size_t index )
const
236 return index >= part.size() ? std::string() : part.at(index) ;
239std::string GPop::ServerProtocol::commandParameter(
const std::string & line_in , std::size_t index )
const
241 return commandPart( line_in , index ) ;
244GPop::ServerProtocol::Event GPop::ServerProtocol::commandEvent(
const std::string & command )
const
246 if( command ==
"QUIT" )
return Event::eQuit ;
247 if( command ==
"STAT" )
return Event::eStat ;
248 if( command ==
"LIST" )
return Event::eList ;
249 if( command ==
"RETR" )
return Event::eRetr ;
250 if( command ==
"DELE" )
return Event::eDele ;
251 if( command ==
"NOOP" )
return Event::eNoop ;
252 if( command ==
"RSET" )
return Event::eRset ;
254 if( command ==
"TOP" )
return Event::eTop ;
255 if( command ==
"UIDL" )
return Event::eUidl ;
256 if( command ==
"USER" )
return Event::eUser ;
257 if( command ==
"PASS" )
return Event::ePass ;
258 if( command ==
"APOP" )
return Event::eApop ;
259 if( command ==
"AUTH" )
return Event::eAuth ;
260 if( command ==
"CAPA" )
return Event::eCapa ;
261 if( command ==
"STLS" )
return Event::eStls ;
263 return Event::eUnknown ;
266void GPop::ServerProtocol::doQuitEarly(
const std::string & ,
bool & )
268 sendLine(
"+OK " + m_text.quit() ) ;
269 throw ProtocolDone() ;
272void GPop::ServerProtocol::doQuit(
const std::string & ,
bool & )
274 m_store_lock.commit() ;
275 sendLine(
"+OK " + m_text.quit() ) ;
276 throw ProtocolDone() ;
279void GPop::ServerProtocol::doStat(
const std::string & ,
bool & )
281 std::ostringstream ss ;
282 ss <<
"+OK " << m_store_lock.messageCount() <<
" " << m_store_lock.totalByteCount() ;
283 sendLine( ss.str() ) ;
286void GPop::ServerProtocol::doUidl(
const std::string & line ,
bool & )
288 sendList( line ,
true ) ;
291void GPop::ServerProtocol::doList(
const std::string & line ,
bool & )
293 sendList( line ,
false ) ;
296void GPop::ServerProtocol::sendList(
const std::string & line ,
bool uidl )
298 std::string id_string = commandParameter( line ) ;
302 if( !id_string.empty() )
304 id = commandNumber( line , -1 ) ;
305 if( !m_store_lock.valid(
id) )
307 sendError(
"invalid id" ) ;
313 bool multi_line =
id == -1 ;
314 GPop::StoreLock::List list = m_store_lock.list(
id ) ;
315 std::ostringstream ss ;
317 if( multi_line ) ss << list.size() <<
" message(s)" << crlf() ;
318 for(
auto & item : list )
320 ss << item.id <<
" " ;
321 if( uidl ) ss << item.uidl ;
322 if( !uidl ) ss << item.size ;
323 if( multi_line ) ss << crlf() ;
332 sendLine( ss.str() ) ;
336void GPop::ServerProtocol::doRetr(
const std::string & line ,
bool & more )
338 int id = commandNumber( line , -1 ) ;
339 if(
id == -1 || !m_store_lock.valid(
id) )
346 m_content = m_store_lock.get(
id) ;
349 std::ostringstream ss ;
350 ss <<
"+OK " << m_store_lock.byteCount(
id) <<
" octets" ;
351 sendLine( ss.str() ) ;
355void GPop::ServerProtocol::doTop(
const std::string & line ,
bool & more )
357 int id = commandNumber( line , -1 , 1U ) ;
358 int n = commandNumber( line , -1 , 2U ) ;
359 G_DEBUG(
"ServerProtocol::doTop: " <<
id <<
", " << n ) ;
360 if(
id == -1 || !m_store_lock.valid(
id) || n < 0 )
367 m_content = m_store_lock.get(
id ) ;
374void GPop::ServerProtocol::doDele(
const std::string & line ,
bool & )
376 int id = commandNumber( line , -1 ) ;
377 if(
id == -1 || !m_store_lock.valid(
id) )
383 m_store_lock.remove(
id ) ;
388void GPop::ServerProtocol::doRset(
const std::string & ,
bool & )
390 m_store_lock.rollback() ;
394void GPop::ServerProtocol::doNoop(
const std::string & ,
bool & )
399void GPop::ServerProtocol::doNothing(
const std::string & ,
bool & )
403void GPop::ServerProtocol::doAuth(
const std::string & line ,
bool & ok )
405 std::string mechanism =
G::Str::upper( commandParameter(line) ) ;
407 if( mechanism.empty() )
411 std::string list = mechanisms() ;
413 std::ostringstream ss ;
414 ss <<
"+OK" << crlf() ;
416 ss << list << crlf() ;
420 else if( m_sasl_server->requiresEncryption() && !m_secure )
425 sendError(
"must use STLS before authentication" ) ;
429 std::string initial_response = commandParameter(line,2) ;
430 if( initial_response ==
"=" )
431 initial_response.clear() ;
433 m_sasl_server_init_apop = false ;
434 if( !m_sasl_server->init( mechanism ) )
437 sendError(
"invalid mechanism" ) ;
439 else if( m_sasl_server->mustChallenge() && !initial_response.empty() )
442 sendError(
"invalid initial response" ) ;
444 else if( !initial_response.empty() )
446 m_fsm.apply( *
this , Event::eAuthData , initial_response ) ;
450 std::string initial_challenge = m_sasl_server->initialChallenge() ;
456void GPop::ServerProtocol::doAuthData(
const std::string & line ,
bool & ok )
467 if( done && m_sasl_server->authenticated() )
469 m_fsm.apply( *
this , Event::eAuthComplete ,
"" ) ;
482void GPop::ServerProtocol::doAuthComplete(
const std::string & ,
bool & )
484 G_LOG_S(
"GPop::ServerProtocol: pop authentication of " << m_sasl_server->id() <<
" connected from " << m_peer_address.displayString() ) ;
485 m_user = m_sasl_server->id() ;
490void GPop::ServerProtocol::lockStore()
492 m_store_lock.lock( m_user ) ;
495void GPop::ServerProtocol::doStls(
const std::string & ,
bool & )
497 G_ASSERT( m_security.securityEnabled() ) ;
499 m_security.securityStart() ;
508bool GPop::ServerProtocol::mechanismsIncludePlain()
const
510 return m_sasl_server->active() && mechanisms().find(
"PLAIN") != std::string::npos ;
513std::string GPop::ServerProtocol::mechanisms()
const
516 return m_sasl_server->active() ? m_sasl_server->mechanisms(
' ') : std::string() ;
519void GPop::ServerProtocol::doCapa(
const std::string & ,
bool & )
521 std::ostringstream ss ;
522 ss <<
"+OK " << m_text.capa() << crlf() ;
526 if( mechanismsIncludePlain() )
527 ss <<
"USER" << crlf() ;
532 <<
"UIDL" << crlf() ;
534 if( m_security.securityEnabled() )
535 ss <<
"STLS" << crlf() ;
537 if( !mechanisms().empty() )
538 ss <<
"SASL " << mechanisms() << crlf() ;
544void GPop::ServerProtocol::doUser(
const std::string & line ,
bool & )
546 if( mechanismsIncludePlain() )
548 m_user = commandParameter(line) ;
549 sendLine(
"+OK " + m_text.user(commandParameter(line)) ) ;
553 sendError(
"no SASL PLAIN mechanism to do USER/PASS authentication" ) ;
557void GPop::ServerProtocol::doPass(
const std::string & line ,
bool & ok )
559 m_sasl_server_init_apop = false ;
560 if( !m_user.empty() && m_sasl_server->init(
"PLAIN") )
562 std::string rsp = m_user + std::string(1U,
'\0') + m_user + std::string(1U,
'\0') + commandParameter(line) ;
564 std::string ignore = m_sasl_server->apply( rsp , done ) ;
565 if( done && m_sasl_server->authenticated() )
583void GPop::ServerProtocol::doApop(
const std::string & line ,
bool & ok )
585 if( m_sasl_server->active() && m_sasl_server_init_apop )
587 std::string rsp = commandParameter(line,1) +
" " + commandParameter(line,2) ;
589 std::string ignore = m_sasl_server->apply( rsp , done ) ;
590 if( done && m_sasl_server->authenticated() )
592 m_user = m_sasl_server->id() ;
609void GPop::ServerProtocol::sendLine( std::string line )
612 line.append( crlf() ) ;
613 m_sender.protocolSend( line , 0U ) ;
616void GPop::ServerProtocol::sendLines( std::ostringstream & ss )
621 const std::string s = ss.str() ;
622 std::size_t lines = std::count( s.begin() , s.end() ,
'\n' ) ;
623 const std::size_t npos = std::string::npos ;
624 std::size_t p0 = 0U ;
625 std::size_t p1 = s.find(
'\n' ) ;
626 for( std::size_t i = 0U ; i < lines ; i++ , p0 = p1+1U , p1 = s.find(
'\n',p0+1U) )
628 G_ASSERT( p0 != npos && p0 < s.size() ) ;
629 std::size_t n = p1 == npos ? (s.size()-p0) : (p1-p0) ;
630 if( n && p1 && p1 != npos && s.at(p1-1U) ==
'\r' ) --n ;
631 if( lines <= 7U || i < 4U || i > (lines-3U) )
632 G_LOG(
"GPop::ServerProtocol: tx>>: \"" <<
G::Str::printable(s.substr(p0,n)) <<
"\"" ) ;
634 G_LOG(
"GPop::ServerProtocol: tx>>: [" << (lines-6U) <<
" lines]" ) ;
635 if( p1 == npos || (p1+1U) == s.size() )
638 m_sender.protocolSend( s , 0U ) ;
642 m_sender.protocolSend( ss.str() , 0U ) ;
646const std::string & GPop::ServerProtocol::crlf()
648 static const std::string s(
"\015\012" ) ;
658std::string GPop::ServerProtocolText::greeting()
const
660 return "POP3 server ready" ;
663std::string GPop::ServerProtocolText::quit()
const
665 return "signing off" ;
668std::string GPop::ServerProtocolText::capa()
const
670 return "capability list follows" ;
673std::string GPop::ServerProtocolText::user(
const std::string &
id )
const
675 return std::string() +
"user: " + id ;
An interface used by GAuth::SaslServer to obtain authentication secrets.
The GNet::Address class encapsulates a TCP/UDP transport address.
ServerProtocolText(const GNet::Address &peer)
Constructor.
An interface used by ServerProtocol to enable TLS.
An interface used by ServerProtocol to send protocol replies.
An interface used by ServerProtocol to provide response text strings.
void resume()
Called when the Sender can send again.
void secure()
Called when the server connection becomes secure.
void init()
Starts the protocol.
void apply(const std::string &line)
Called on receipt of a string from the client.
ServerProtocol(Sender &sender, Security &security, Store &store, const GAuth::SaslServerSecrets &server_secrets, const std::string &sasl_server_config, const Text &text, const GNet::Address &peer_address, const Config &config)
Constructor.
static std::string decode(const std::string &, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
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.
static LogOutput * instance() noexcept
Returns a pointer to the controlling LogOutput object.
static string_view ws()
Returns a string of standard whitespace characters.
static void splitIntoTokens(const std::string &in, StringArray &out, string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
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...
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 ...
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
static int toInt(const std::string &s)
Converts string 's' to an int.
static std::string readLineFrom(std::istream &stream, const std::string &eol=std::string())
Reads a line from the stream using the given line terminator.
static bool enabled() noexcept
Returns true if test features are enabled.
SASL authentication classes.
std::vector< std::string > StringArray
A std::vector of std::strings.
A structure containing configuration parameters for ServerProtocol, currently empty.