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_fsm( Event::eStat , State::sActive , State::sActive , &ServerProtocol::doStat ) ;
48 m_fsm( Event::eList , State::sActive , State::sActive , &ServerProtocol::doList ) ;
49 m_fsm( Event::eRetr , State::sActive , State::sData , &ServerProtocol::doRetr , State::sActive ) ;
50 m_fsm( Event::eTop , State::sActive , State::sData , &ServerProtocol::doTop , State::sActive ) ;
51 m_fsm( Event::eDele , State::sActive , State::sActive , &ServerProtocol::doDele ) ;
52 m_fsm( Event::eNoop , State::sActive , State::sActive , &ServerProtocol::doNoop ) ;
53 m_fsm( Event::eRset , State::sActive , State::sActive , &ServerProtocol::doRset ) ;
54 m_fsm( Event::eUidl , State::sActive , State::sActive , &ServerProtocol::doUidl ) ;
55 m_fsm( Event::eSent , State::sData , State::sActive , &ServerProtocol::doNothing ) ;
56 m_fsm( Event::eUser , State::sStart , State::sStart , &ServerProtocol::doUser ) ;
57 m_fsm( Event::ePass , State::sStart , State::sActive , &ServerProtocol::doPass , State::sStart ) ;
58 m_fsm( Event::eApop , State::sStart , State::sActive , &ServerProtocol::doApop , State::sStart ) ;
59 m_fsm( Event::eQuit , State::sStart , State::sEnd , &ServerProtocol::doQuitEarly ) ;
60 m_fsm( Event::eCapa , State::sStart , State::sStart , &ServerProtocol::doCapa ) ;
61 m_fsm( Event::eCapa , State::sActive , State::sActive , &ServerProtocol::doCapa ) ;
62 if( m_security.securityEnabled() )
63 m_fsm( Event::eStls , State::sStart , State::sStart , &ServerProtocol::doStls , State::sStart ) ;
64 m_fsm( Event::eAuth , State::sStart , State::sAuth , &ServerProtocol::doAuth , State::sStart ) ;
65 m_fsm( Event::eAuthData , State::sAuth , State::sAuth , &ServerProtocol::doAuthData , State::sStart ) ;
66 m_fsm( Event::eAuthComplete , State::sAuth , State::sActive , &ServerProtocol::doAuthComplete ) ;
67 m_fsm( Event::eCapa , State::sActive , State::sActive , &ServerProtocol::doCapa ) ;
68 m_fsm( Event::eQuit , State::sActive , State::sEnd , &ServerProtocol::doQuit ) ;
76void GPop::ServerProtocol::sendInit()
78 std::string greeting = std::string(
"+OK ",4U).append(m_text.greeting()) ;
79 if( m_sasl->init( m_secure ,
"APOP" ) )
81 m_sasl_init_apop = true ;
82 std::string apop_challenge = m_sasl->initialChallenge() ;
83 if( !apop_challenge.empty() )
85 greeting.append( 1U ,
' ' ) ;
86 greeting.append( apop_challenge ) ;
89 sendLine( std::move(greeting) ) ;
92void GPop::ServerProtocol::sendOk()
94 sendLine(
"+OK\r\n"_sv ,
true ) ;
97void GPop::ServerProtocol::sendError(
const std::string & more )
102 sendLine( std::string(
"-ERR ",5U).append(more) ) ;
105void GPop::ServerProtocol::sendError()
107 sendLine(
"-ERR\r\n"_sv ,
true ) ;
113 Event
event = m_fsm.state() == State::sAuth ? Event::eAuthData : commandEvent(commandWord(line)) ;
117 if( event == Event::ePass )
118 log_text = (commandPart(line,0U)+
" [password not logged]") ;
119 if( event == Event::eAuthData || event == Event::eAuthComplete )
120 log_text =
"[authentication response not logged]" ;
121 if( event == Event::eAuth && !commandPart(line,1U).empty() )
122 log_text = commandPart(line,0U) +
" " + commandPart(line,1U) ;
123 G_LOG(
"GPop::ServerProtocol: rx<<: \"" << log_text <<
"\"" ) ;
126 State new_state = m_fsm.apply( *
this , event , line ) ;
127 const bool protocol_error = new_state == State::s_Any ;
130 G_DEBUG(
"GPop::ServerProtocol::apply: protocol error: " <<
static_cast<int>(event) <<
" " <<
static_cast<int>(m_fsm.state()) ) ;
135 if( new_state == State::sData )
139void GPop::ServerProtocol::sendContent()
142 std::string line( 200 ,
'.' ) ;
145 while( sendContentLine(line,eot) && !eot )
148 G_LOG(
"GPop::ServerProtocol: tx>>: [" << n <<
" line(s) of content]" ) ;
151 G_LOG(
"GPop::ServerProtocol: tx>>: \".\"" ) ;
153 m_fsm.apply( *
this , Event::eSent ,
"" ) ;
163 G_DEBUG(
"GPop::ServerProtocol::resume: flow control released" ) ;
164 if( m_fsm.state() == State::sData )
168bool GPop::ServerProtocol::sendContentLine( std::string & line ,
bool & eot )
170 G_ASSERT( m_content !=
nullptr ) ;
172 bool limited = m_in_body && m_body_limit == 0L ;
173 if( m_body_limit > 0L && m_in_body )
178 m_config.crlf_only ? G::Str::Eol::CrLf : G::Str::Eol::Cr_Lf_CrLf ,
181 eot = eof || limited ;
182 if( eot ) line.erase( 1U ) ;
183 line.append(
"\r\n" , 2U ) ;
184 std::size_t offset = eot ? 0U : ( line.at(1U) ==
'.' ? 0U : 1U ) ;
186 if( !m_in_body && line.length() == (offset+2U) )
189 return m_sender.protocolSend( line , offset ) ;
192int GPop::ServerProtocol::commandNumber(
const std::string & line ,
int default_ , std::size_t index )
const
194 int number = default_ ;
199 catch( G::Str::Overflow & )
202 catch( G::Str::InvalidFormat & )
208std::string GPop::ServerProtocol::commandWord(
const std::string & line )
const
213std::string GPop::ServerProtocol::commandPart(
const std::string & line , std::size_t index )
215 std::string_view line_sv( line ) ;
217 for( std::size_t i = 0 ; i < index ; ++t , i++ )
219 return t.valid() ? G::sv_to_string(t()) :
std::string() ;
222std::string GPop::ServerProtocol::commandParameter(
const std::string & line_in , std::size_t index )
const
224 return commandPart( line_in , index ) ;
227GPop::ServerProtocol::Event GPop::ServerProtocol::commandEvent( std::string_view command )
229 if( command ==
"QUIT"_sv )
return Event::eQuit ;
230 if( command ==
"STAT"_sv )
return Event::eStat ;
231 if( command ==
"LIST"_sv )
return Event::eList ;
232 if( command ==
"RETR"_sv )
return Event::eRetr ;
233 if( command ==
"DELE"_sv )
return Event::eDele ;
234 if( command ==
"NOOP"_sv )
return Event::eNoop ;
235 if( command ==
"RSET"_sv )
return Event::eRset ;
237 if( command ==
"TOP"_sv )
return Event::eTop ;
238 if( command ==
"UIDL"_sv )
return Event::eUidl ;
239 if( command ==
"USER"_sv )
return Event::eUser ;
240 if( command ==
"PASS"_sv )
return Event::ePass ;
241 if( command ==
"APOP"_sv )
return Event::eApop ;
242 if( command ==
"AUTH"_sv )
return Event::eAuth ;
243 if( command ==
"CAPA"_sv )
return Event::eCapa ;
244 if( command ==
"STLS"_sv )
return Event::eStls ;
246 return Event::eUnknown ;
249void GPop::ServerProtocol::doQuitEarly(
const std::string & ,
bool & )
251 sendLine( std::string(
"+OK ",4).append(m_text.quit()) ) ;
252 throw ProtocolDone() ;
255void GPop::ServerProtocol::doQuit(
const std::string & ,
bool & )
257 m_store_list.commit() ;
258 sendLine( std::string(
"+OK ",4U).append(m_text.quit()) ) ;
259 throw ProtocolDone() ;
262void GPop::ServerProtocol::doStat(
const std::string & ,
bool & )
264 sendLine( std::string(
"+OK ",4U)
265 .append(std::to_string(m_store_list.messageCount()))
267 .append(std::to_string(m_store_list.totalByteCount())) ) ;
270void GPop::ServerProtocol::doUidl(
const std::string & line ,
bool & )
272 sendList( line ,
true ) ;
275void GPop::ServerProtocol::doList(
const std::string & line ,
bool & )
277 sendList( line ,
false ) ;
280void GPop::ServerProtocol::sendList(
const std::string & line ,
bool uidl )
282 std::string id_string = commandParameter( line ) ;
286 if( !id_string.empty() )
288 id = commandNumber( line , -1 ) ;
289 if( !m_store_list.valid(
id) )
291 sendError(
"invalid id" ) ;
297 std::ostringstream ss ;
299 bool multi_line =
id == -1 ;
302 ss << m_store_list.messageCount() <<
" message(s)" <<
"\r\n" ;
304 for(
auto item_p = m_store_list.cbegin() ; item_p != m_store_list.cend() ; ++item_p , ++i )
306 const auto & item = *item_p ;
310 if( uidl ) ss << item.uidl() ;
311 if( !uidl ) ss << item.size ;
320 auto item = m_store_list.get(
id ) ;
322 if( uidl ) ss << item.uidl() ;
323 if( !uidl ) ss << item.size ;
324 sendLine( ss.str() ) ;
328void GPop::ServerProtocol::doRetr(
const std::string & line ,
bool & more )
330 int id = commandNumber( line , -1 ) ;
331 if(
id == -1 || !m_store_list.valid(
id) )
338 m_content = m_store_list.content(
id) ;
341 std::ostringstream ss ;
342 ss <<
"+OK " << m_store_list.byteCount(
id) <<
" octets" ;
343 sendLine( ss.str() ) ;
347void GPop::ServerProtocol::doTop(
const std::string & line ,
bool & more )
349 int id = commandNumber( line , -1 , 1U ) ;
350 int n = commandNumber( line , -1 , 2U ) ;
351 G_DEBUG(
"ServerProtocol::doTop: " <<
id <<
", " << n ) ;
352 if(
id == -1 || !m_store_list.valid(
id) || n < 0 )
359 m_content = m_store_list.content(
id ) ;
366void GPop::ServerProtocol::doDele(
const std::string & line ,
bool & )
368 int id = commandNumber( line , -1 ) ;
369 if(
id == -1 || !m_store_list.valid(
id) )
375 m_store_list.remove(
id ) ;
380void GPop::ServerProtocol::doRset(
const std::string & ,
bool & )
382 m_store_list.rollback() ;
386void GPop::ServerProtocol::doNoop(
const std::string & ,
bool & )
391void GPop::ServerProtocol::doNothing(
const std::string & ,
bool & )
395void GPop::ServerProtocol::doAuth(
const std::string & line ,
bool & ok )
397 std::string mechanism =
G::Str::upper( commandParameter(line) ) ;
399 if( mechanism.empty() )
403 std::string list = mechanisms() ;
405 std::ostringstream ss ;
408 ss << list <<
"\r\n" ;
412 else if( mechanisms().empty() )
415 sendError(
"must use STLS before authentication" ) ;
419 std::string initial_response = commandParameter(line,2) ;
420 if( initial_response ==
"=" )
421 initial_response.clear() ;
423 m_sasl_init_apop = false ;
424 if( !m_sasl->init( m_secure , mechanism ) )
427 sendError(
"invalid mechanism" ) ;
429 else if( m_sasl->mustChallenge() && !initial_response.empty() )
432 sendError(
"invalid initial response" ) ;
434 else if( !initial_response.empty() )
436 m_fsm.apply( *
this , Event::eAuthData , initial_response ) ;
440 std::string initial_challenge = m_sasl->initialChallenge() ;
446void GPop::ServerProtocol::doAuthData(
const std::string & line ,
bool & ok )
457 if( done && m_sasl->authenticated() )
459 m_fsm.apply( *
this , Event::eAuthComplete ,
"" ) ;
472void GPop::ServerProtocol::doAuthComplete(
const std::string & ,
bool & )
474 G_LOG_S(
"GPop::ServerProtocol: pop authentication of " << m_sasl->id() <<
" connected from " << m_peer_address.displayString() ) ;
475 m_user = m_sasl->id() ;
476 m_store.prepare( m_user ) ;
477 readStore( m_user ) ;
481void GPop::ServerProtocol::readStore(
const std::string & user )
483 m_store_user = std::make_unique<StoreUser>( m_store , user ) ;
484 m_store_list = StoreList( *m_store_user , m_store.allowDelete() ) ;
487void GPop::ServerProtocol::doStls(
const std::string & ,
bool & )
489 G_ASSERT( m_security.securityEnabled() ) ;
491 m_security.securityStart() ;
500bool GPop::ServerProtocol::mechanismsIncludePlain()
const
502 return mechanisms().find(
"PLAIN") != std::string::npos ;
505std::string GPop::ServerProtocol::mechanisms()
const
508 m.erase( std::remove( m.begin() , m.end() ,
"APOP" ) , m.end() ) ;
512void GPop::ServerProtocol::doCapa(
const std::string & ,
bool & )
514 std::ostringstream ss ;
515 ss <<
"+OK " << m_text.capa() <<
"\r\n" ;
519 if( mechanismsIncludePlain() )
522 ss <<
"CAPA\r\nTOP\r\nUIDL\r\n" ;
524 if( m_security.securityEnabled() )
527 if( !mechanisms().empty() )
528 ss <<
"SASL " << mechanisms() <<
"\r\n" ;
534void GPop::ServerProtocol::doUser(
const std::string & line ,
bool & )
536 if( mechanismsIncludePlain() )
538 m_user = commandParameter(line) ;
539 sendLine( std::string(
"+OK ",4U).append(m_text.user(commandParameter(line))) ) ;
543 sendError(
"no SASL PLAIN mechanism to do USER/PASS authentication" ) ;
547void GPop::ServerProtocol::doPass(
const std::string & line ,
bool & ok )
549 m_sasl_init_apop = false ;
550 if( !m_user.empty() && m_sasl->init(m_secure,
"PLAIN") )
552 std::string rsp = m_user + std::string(1U,
'\0').append(m_user).append(1U,
'\0').append(commandParameter(line)) ;
554 GDEF_IGNORE_RETURN m_sasl->apply( rsp , done ) ;
555 if( done && m_sasl->authenticated() )
557 m_store.prepare( m_user ) ;
558 readStore( m_user ) ;
574void GPop::ServerProtocol::doApop(
const std::string & line ,
bool & ok )
576 if( m_sasl_init_apop )
578 std::string rsp = commandParameter(line,1) +
" " + commandParameter(line,2) ;
580 GDEF_IGNORE_RETURN m_sasl->apply( rsp , done ) ;
581 if( done && m_sasl->authenticated() )
583 m_user = m_sasl->id() ;
584 m_store.prepare( m_user ) ;
585 readStore( m_user ) ;
601void GPop::ServerProtocol::sendLine( std::string_view line ,
bool has_crlf )
606 m_sender.protocolSend( line , 0U ) ;
611 m_sender.protocolSend( std::string(line.data(),line.size()).append(
"\r\n",2U) , 0U ) ;
615void GPop::ServerProtocol::sendLine( std::string && line )
618 m_sender.protocolSend( line.append(
"\r\n",2U) , 0U ) ;
621void GPop::ServerProtocol::sendLines( std::ostringstream & ss )
624 const std::string s = ss.str() ;
625 if( G::LogOutput::Instance::atVerbose() )
627 std::size_t lines = std::count( s.begin() , s.end() ,
'\n' ) ;
628 const std::size_t npos = std::string::npos ;
629 std::size_t p0 = 0U ;
630 std::size_t p1 = s.find(
'\n' ) ;
631 for( std::size_t i = 0U ; i < lines ; i++ , p0 = p1+1U , p1 = s.find(
'\n',p0+1U) )
633 G_ASSERT( p0 != npos && p0 < s.size() ) ;
634 std::size_t n = p1 == npos ? (s.size()-p0) : (p1-p0) ;
635 if( n && p1 && p1 != npos && s.at(p1-1U) ==
'\r' ) --n ;
636 if( lines <= 7U || i < 4U || i > (lines-3U) )
637 G_LOG(
"GPop::ServerProtocol: tx>>: \"" <<
G::Str::printable(s.substr(p0,n)) <<
"\"" ) ;
639 G_LOG(
"GPop::ServerProtocol: tx>>: [" << (lines-6U) <<
" lines]" ) ;
640 if( p1 == npos || (p1+1U) == s.size() )
644 m_sender.protocolSend( s , 0U ) ;
653std::string GPop::ServerProtocolText::greeting()
const
655 return "POP3 server ready" ;
658std::string GPop::ServerProtocolText::quit()
const
660 return "signing off" ;
663std::string GPop::ServerProtocolText::capa()
const
665 return "capability list follows" ;
668std::string GPop::ServerProtocolText::user(
const std::string &
id )
const
670 return std::string(
"user: ",6U).append(
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 encode(std::string_view, std::string_view line_break={})
Encodes the given string, optionally inserting line-breaks to limit the line length.
static std::string decode(std::string_view, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
static std::string_view trimmedView(std::string_view s, std::string_view ws) noexcept
Returns a trim()med view of the input view.
static int toInt(std::string_view s)
Converts string 's' to an int.
static std::string join(std::string_view sep, const StringArray &strings)
Concatenates an array of strings with separators.
static std::istream & readLine(std::istream &stream, std::string &result, std::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 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(std::string_view)
Returns a copy of 's' in which all seven-bit lower-case characters have been replaced by upper-case c...
static unsigned int replaceAll(std::string &s, std::string_view from, std::string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
static std::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,...
SASL authentication classes.
std::vector< std::string > StringArray
A std::vector of std::strings.
A structure containing configuration parameters for ServerProtocol.