38 m_security(security) ,
41 m_sasl(
GAuth::SaslServerFactory::newSaslServer(server_secrets,true,sasl_server_config,config.sasl_server_challenge_domain)) ,
42 m_peer_address(peer_address) ,
43 m_fsm(State::sStart,State::sEnd,State::s_Same,State::s_Any) ,
47 m_sasl_init_apop(false)
51 m_fsm( Event::eStat , State::sActive , State::sActive , &GPop::ServerProtocol::doStat ) ;
52 m_fsm( Event::eList , State::sActive , State::sActive , &GPop::ServerProtocol::doList ) ;
53 m_fsm( Event::eRetr , State::sActive , State::sData , &GPop::ServerProtocol::doRetr , State::sActive ) ;
54 m_fsm( Event::eTop , State::sActive , State::sData , &GPop::ServerProtocol::doTop , State::sActive ) ;
55 m_fsm( Event::eDele , State::sActive , State::sActive , &GPop::ServerProtocol::doDele ) ;
56 m_fsm( Event::eNoop , State::sActive , State::sActive , &GPop::ServerProtocol::doNoop ) ;
57 m_fsm( Event::eRset , State::sActive , State::sActive , &GPop::ServerProtocol::doRset ) ;
58 m_fsm( Event::eUidl , State::sActive , State::sActive , &GPop::ServerProtocol::doUidl ) ;
59 m_fsm( Event::eSent , State::sData , State::sActive , &GPop::ServerProtocol::doNothing ) ;
60 m_fsm( Event::eUser , State::sStart , State::sStart , &GPop::ServerProtocol::doUser ) ;
61 m_fsm( Event::ePass , State::sStart , State::sActive , &GPop::ServerProtocol::doPass , State::sStart ) ;
62 m_fsm( Event::eApop , State::sStart , State::sActive , &GPop::ServerProtocol::doApop , State::sStart ) ;
63 m_fsm( Event::eQuit , State::sStart , State::sEnd , &GPop::ServerProtocol::doQuitEarly ) ;
64 m_fsm( Event::eCapa , State::sStart , State::sStart , &GPop::ServerProtocol::doCapa ) ;
65 m_fsm( Event::eCapa , State::sActive , State::sActive , &GPop::ServerProtocol::doCapa ) ;
66 if( m_security.securityEnabled() )
67 m_fsm( Event::eStls , State::sStart , State::sStart , &GPop::ServerProtocol::doStls , State::sStart ) ;
68 m_fsm( Event::eAuth , State::sStart , State::sAuth , &GPop::ServerProtocol::doAuth , State::sStart ) ;
69 m_fsm( Event::eAuthData , State::sAuth , State::sAuth , &GPop::ServerProtocol::doAuthData , State::sStart ) ;
70 m_fsm( Event::eAuthComplete , State::sAuth , State::sActive , &GPop::ServerProtocol::doAuthComplete ) ;
71 m_fsm( Event::eCapa , State::sActive , State::sActive , &GPop::ServerProtocol::doCapa ) ;
72 m_fsm( Event::eQuit , State::sActive , State::sEnd , &GPop::ServerProtocol::doQuit ) ;
80void GPop::ServerProtocol::sendInit()
82 std::string greeting = std::string(
"+OK ",4U).append(m_text.greeting()) ;
83 if( m_sasl->init( m_secure ,
"APOP" ) )
85 m_sasl_init_apop = true ;
86 std::string apop_challenge = m_sasl->initialChallenge() ;
87 if( !apop_challenge.empty() )
89 greeting.append( 1U ,
' ' ) ;
90 greeting.append( apop_challenge ) ;
93 sendLine( std::move(greeting) ) ;
96void GPop::ServerProtocol::sendOk()
98 sendLine(
"+OK\r\n"_sv ,
true ) ;
101void GPop::ServerProtocol::sendError(
const std::string & more )
106 sendLine( std::string(
"-ERR ",5U).append(more) ) ;
109void GPop::ServerProtocol::sendError()
111 sendLine(
"-ERR\r\n"_sv ,
true ) ;
117 Event
event = m_fsm.state() == State::sAuth ? Event::eAuthData : commandEvent(commandWord(line)) ;
121 if( event == Event::ePass )
122 log_text = (commandPart(line,0U)+
" [password not logged]") ;
123 if( event == Event::eAuthData || event == Event::eAuthComplete )
124 log_text =
"[authentication response not logged]" ;
125 if( event == Event::eAuth && !commandPart(line,1U).empty() )
126 log_text = commandPart(line,0U) +
" " + commandPart(line,1U) ;
127 G_LOG(
"GPop::ServerProtocol: rx<<: \"" << log_text <<
"\"" ) ;
130 State new_state = m_fsm.apply( *
this , event , line ) ;
131 const bool protocol_error = new_state == State::s_Any ;
134 G_DEBUG(
"GPop::ServerProtocol::apply: protocol error: " <<
static_cast<int>(event) <<
" " <<
static_cast<int>(m_fsm.state()) ) ;
139 if( new_state == State::sData )
143void GPop::ServerProtocol::sendContent()
146 std::string line( 200 ,
'.' ) ;
149 while( sendContentLine(line,eot) && !eot )
152 G_LOG(
"GPop::ServerProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
155 G_LOG(
"GPop::ServerProtocol: tx>>: \".\"" ) ;
157 m_fsm.apply( *
this , Event::eSent ,
"" ) ;
167 G_DEBUG(
"GPop::ServerProtocol::resume: flow control released" ) ;
168 if( m_fsm.state() == State::sData )
172bool GPop::ServerProtocol::sendContentLine( std::string & line ,
bool & eot )
174 G_ASSERT( m_content !=
nullptr ) ;
176 bool limited = m_in_body && m_body_limit == 0L ;
177 if( m_body_limit > 0L && m_in_body )
182 m_config.crlf_only ? G::Str::Eol::CrLf : G::Str::Eol::Cr_Lf_CrLf ,
185 eot = eof || limited ;
186 if( eot ) line.erase( 1U ) ;
187 line.append(
"\r\n" , 2U ) ;
188 std::size_t offset = eot ? 0U : ( line.at(1U) ==
'.' ? 0U : 1U ) ;
190 if( !m_in_body && line.length() == (offset+2U) )
193 return m_sender.protocolSend( line , offset ) ;
196int GPop::ServerProtocol::commandNumber(
const std::string & line ,
int default_ , std::size_t index )
const
198 int number = default_ ;
203 catch( G::Str::Overflow & )
206 catch( G::Str::InvalidFormat & )
212std::string GPop::ServerProtocol::commandWord(
const std::string & line )
const
217std::string GPop::ServerProtocol::commandPart(
const std::string & line , std::size_t index )
const
221 for( std::size_t i = 0 ; i < index ; ++t , i++ )
223 return t.valid() ? G::sv_to_string(t()) :
std::string() ;
226std::string GPop::ServerProtocol::commandParameter(
const std::string & line_in , std::size_t index )
const
228 return commandPart( line_in , index ) ;
231GPop::ServerProtocol::Event GPop::ServerProtocol::commandEvent(
G::string_view command )
const
233 if( command ==
"QUIT"_sv )
return Event::eQuit ;
234 if( command ==
"STAT"_sv )
return Event::eStat ;
235 if( command ==
"LIST"_sv )
return Event::eList ;
236 if( command ==
"RETR"_sv )
return Event::eRetr ;
237 if( command ==
"DELE"_sv )
return Event::eDele ;
238 if( command ==
"NOOP"_sv )
return Event::eNoop ;
239 if( command ==
"RSET"_sv )
return Event::eRset ;
241 if( command ==
"TOP"_sv )
return Event::eTop ;
242 if( command ==
"UIDL"_sv )
return Event::eUidl ;
243 if( command ==
"USER"_sv )
return Event::eUser ;
244 if( command ==
"PASS"_sv )
return Event::ePass ;
245 if( command ==
"APOP"_sv )
return Event::eApop ;
246 if( command ==
"AUTH"_sv )
return Event::eAuth ;
247 if( command ==
"CAPA"_sv )
return Event::eCapa ;
248 if( command ==
"STLS"_sv )
return Event::eStls ;
250 return Event::eUnknown ;
253void GPop::ServerProtocol::doQuitEarly(
const std::string & ,
bool & )
255 sendLine( std::string(
"+OK ",4).append(m_text.quit()) ) ;
256 throw ProtocolDone() ;
259void GPop::ServerProtocol::doQuit(
const std::string & ,
bool & )
261 m_store_list.commit() ;
262 sendLine( std::string(
"+OK ",4U).append(m_text.quit()) ) ;
263 throw ProtocolDone() ;
266void GPop::ServerProtocol::doStat(
const std::string & ,
bool & )
268 sendLine( std::string(
"+OK ",4U)
269 .append(std::to_string(m_store_list.messageCount()))
271 .append(std::to_string(m_store_list.totalByteCount())) ) ;
274void GPop::ServerProtocol::doUidl(
const std::string & line ,
bool & )
276 sendList( line ,
true ) ;
279void GPop::ServerProtocol::doList(
const std::string & line ,
bool & )
281 sendList( line ,
false ) ;
284void GPop::ServerProtocol::sendList(
const std::string & line ,
bool uidl )
286 std::string id_string = commandParameter( line ) ;
290 if( !id_string.empty() )
292 id = commandNumber( line , -1 ) ;
293 if( !m_store_list.valid(
id) )
295 sendError(
"invalid id" ) ;
301 std::ostringstream ss ;
303 bool multi_line =
id == -1 ;
306 ss << m_store_list.messageCount() <<
" message(s)" <<
"\r\n" ;
308 for(
auto item_p = m_store_list.cbegin() ; item_p != m_store_list.cend() ; ++item_p , ++i )
310 const auto & item = *item_p ;
314 if( uidl ) ss << item.uidl() ;
315 if( !uidl ) ss << item.size ;
324 auto item = m_store_list.get(
id ) ;
326 if( uidl ) ss << item.uidl() ;
327 if( !uidl ) ss << item.size ;
328 sendLine( ss.str() ) ;
332void GPop::ServerProtocol::doRetr(
const std::string & line ,
bool & more )
334 int id = commandNumber( line , -1 ) ;
335 if(
id == -1 || !m_store_list.valid(
id) )
342 m_content = m_store_list.content(
id) ;
345 std::ostringstream ss ;
346 ss <<
"+OK " << m_store_list.byteCount(
id) <<
" octets" ;
347 sendLine( ss.str() ) ;
351void GPop::ServerProtocol::doTop(
const std::string & line ,
bool & more )
353 int id = commandNumber( line , -1 , 1U ) ;
354 int n = commandNumber( line , -1 , 2U ) ;
355 G_DEBUG(
"ServerProtocol::doTop: " <<
id <<
", " << n ) ;
356 if(
id == -1 || !m_store_list.valid(
id) || n < 0 )
363 m_content = m_store_list.content(
id ) ;
370void GPop::ServerProtocol::doDele(
const std::string & line ,
bool & )
372 int id = commandNumber( line , -1 ) ;
373 if(
id == -1 || !m_store_list.valid(
id) )
379 m_store_list.remove(
id ) ;
384void GPop::ServerProtocol::doRset(
const std::string & ,
bool & )
386 m_store_list.rollback() ;
390void GPop::ServerProtocol::doNoop(
const std::string & ,
bool & )
395void GPop::ServerProtocol::doNothing(
const std::string & ,
bool & )
399void GPop::ServerProtocol::doAuth(
const std::string & line ,
bool & ok )
401 std::string mechanism =
G::Str::upper( commandParameter(line) ) ;
403 if( mechanism.empty() )
407 std::string list = mechanisms() ;
409 std::ostringstream ss ;
412 ss << list <<
"\r\n" ;
416 else if( mechanisms().empty() )
419 sendError(
"must use STLS before authentication" ) ;
423 std::string initial_response = commandParameter(line,2) ;
424 if( initial_response ==
"=" )
425 initial_response.clear() ;
427 m_sasl_init_apop = false ;
428 if( !m_sasl->init( m_secure , mechanism ) )
431 sendError(
"invalid mechanism" ) ;
433 else if( m_sasl->mustChallenge() && !initial_response.empty() )
436 sendError(
"invalid initial response" ) ;
438 else if( !initial_response.empty() )
440 m_fsm.apply( *
this , Event::eAuthData , initial_response ) ;
444 std::string initial_challenge = m_sasl->initialChallenge() ;
450void GPop::ServerProtocol::doAuthData(
const std::string & line ,
bool & ok )
461 if( done && m_sasl->authenticated() )
463 m_fsm.apply( *
this , Event::eAuthComplete ,
"" ) ;
476void GPop::ServerProtocol::doAuthComplete(
const std::string & ,
bool & )
478 G_LOG_S(
"GPop::ServerProtocol: pop authentication of " << m_sasl->id() <<
" connected from " << m_peer_address.displayString() ) ;
479 m_user = m_sasl->id() ;
480 readStore( m_user ) ;
484void GPop::ServerProtocol::readStore(
const std::string & user )
486 m_store_user = std::make_unique<StoreUser>( m_store , user ) ;
487 m_store_list = StoreList( *m_store_user , m_store.allowDelete() ) ;
490void GPop::ServerProtocol::doStls(
const std::string & ,
bool & )
492 G_ASSERT( m_security.securityEnabled() ) ;
494 m_security.securityStart() ;
503bool GPop::ServerProtocol::mechanismsIncludePlain()
const
505 return mechanisms().find(
"PLAIN") != std::string::npos ;
508std::string GPop::ServerProtocol::mechanisms()
const
511 m.erase( std::remove( m.begin() , m.end() ,
"APOP" ) , m.end() ) ;
515void GPop::ServerProtocol::doCapa(
const std::string & ,
bool & )
517 std::ostringstream ss ;
518 ss <<
"+OK " << m_text.capa() <<
"\r\n" ;
522 if( mechanismsIncludePlain() )
525 ss <<
"CAPA\r\nTOP\r\nUIDL\r\n" ;
527 if( m_security.securityEnabled() )
530 if( !mechanisms().empty() )
531 ss <<
"SASL " << mechanisms() <<
"\r\n" ;
537void GPop::ServerProtocol::doUser(
const std::string & line ,
bool & )
539 if( mechanismsIncludePlain() )
541 m_user = commandParameter(line) ;
542 sendLine( std::string(
"+OK ",4U).append(m_text.user(commandParameter(line))) ) ;
546 sendError(
"no SASL PLAIN mechanism to do USER/PASS authentication" ) ;
550void GPop::ServerProtocol::doPass(
const std::string & line ,
bool & ok )
552 m_sasl_init_apop = false ;
553 if( !m_user.empty() && m_sasl->init(m_secure,
"PLAIN") )
555 std::string rsp = m_user + std::string(1U,
'\0') + m_user + std::string(1U,
'\0') + commandParameter(line) ;
557 std::string ignore = m_sasl->apply( rsp , done ) ;
558 if( done && m_sasl->authenticated() )
560 readStore( m_user ) ;
576void GPop::ServerProtocol::doApop(
const std::string & line ,
bool & ok )
578 if( m_sasl_init_apop )
580 std::string rsp = commandParameter(line,1) +
" " + commandParameter(line,2) ;
582 std::string ignore = m_sasl->apply( rsp , done ) ;
583 if( done && m_sasl->authenticated() )
585 m_user = m_sasl->id() ;
586 readStore( m_user ) ;
602void GPop::ServerProtocol::sendLine(
G::string_view line ,
bool has_crlf )
607 m_sender.protocolSend( line , 0U ) ;
612 m_sender.protocolSend( std::string(line.data(),line.size()).append(
"\r\n",2U) , 0U ) ;
616void GPop::ServerProtocol::sendLine( std::string && line )
619 m_sender.protocolSend( line.append(
"\r\n",2U) , 0U ) ;
622void GPop::ServerProtocol::sendLines( std::ostringstream & ss )
625 const std::string s = ss.str() ;
628 std::size_t lines = std::count( s.begin() , s.end() ,
'\n' ) ;
629 const std::size_t npos = std::string::npos ;
630 std::size_t p0 = 0U ;
631 std::size_t p1 = s.find(
'\n' ) ;
632 for( std::size_t i = 0U ; i < lines ; i++ , p0 = p1+1U , p1 = s.find(
'\n',p0+1U) )
634 G_ASSERT( p0 != npos && p0 < s.size() ) ;
635 std::size_t n = p1 == npos ? (s.size()-p0) : (p1-p0) ;
636 if( n && p1 && p1 != npos && s.at(p1-1U) ==
'\r' ) --n ;
637 if( lines <= 7U || i < 4U || i > (lines-3U) )
638 G_LOG(
"GPop::ServerProtocol: tx>>: \"" <<
G::Str::printable(s.substr(p0,n)) <<
"\"" ) ;
640 G_LOG(
"GPop::ServerProtocol: tx>>: [" << (lines-6U) <<
" lines]" ) ;
641 if( p1 == npos || (p1+1U) == s.size() )
645 m_sender.protocolSend( s , 0U ) ;
654std::string GPop::ServerProtocolText::greeting()
const
656 return "POP3 server ready" ;
659std::string GPop::ServerProtocolText::quit()
const
661 return "signing off" ;
664std::string GPop::ServerProtocolText::capa()
const
666 return "capability list follows" ;
669std::string GPop::ServerProtocolText::user(
const std::string &
id )
const
671 return std::string(
"user: ",6U).append(
id) ;
676GPop::ServerProtocol::Config::Config()
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(string_view, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
static std::string encode(string_view, string_view line_break={})
Encodes the given string, optionally inserting line-breaks to limit the line length.
static bool atVerbose() noexcept
Returns at(Severity::InfoVerbose).
static std::istream & readLine(std::istream &stream, std::string &result, string_view eol={}, bool pre_erase_result=true, std::size_t limit=0U)
Reads a line from the stream using the given line terminator, which may be multi-character.
static int toInt(string_view s)
Converts string 's' to an int.
static unsigned int replaceAll(std::string &s, string_view from, string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
static string_view trimmedView(string_view s, string_view ws) noexcept
Returns a trim()med view of the input view.
static std::string join(string_view sep, const StringArray &strings)
Concatenates an array of strings with separators.
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 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...
static string_view ws() noexcept
Returns a string of standard whitespace characters.
A zero-copy string token iterator where the token separators are runs of whitespace characters,...
A class like c++17's std::string_view.
SASL authentication classes.
std::vector< std::string > StringArray
A std::vector of std::strings.
A structure containing configuration parameters for ServerProtocol.