E-MailRelay
gadminserver.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 gadminserver.cpp
19///
20
21#include "gdef.h"
22#include "geventloop.h"
23#include "gnetdone.h"
24#include "gadminserver.h"
25#include "gmessagestore.h"
26#include "gstoredmessage.h"
27#include "gprocess.h"
28#include "glocal.h"
29#include "gmonitor.h"
30#include "gslot.h"
31#include "gstr.h"
32#include <utility>
33
35 AdminServer & server , const std::string & remote_address ,
36 const G::StringMap & info_commands , const G::StringMap & config_commands ,
37 bool with_terminate ) :
38 GNet::ServerPeer(esu.bind(this),peer_info,GNet::LineBufferConfig::autodetect()) ,
39 m_es(esu.bind(this)) ,
40 m_server(server) ,
41 m_prompt("E-MailRelay> ") ,
42 m_blocked(false) ,
43 m_remote_address(remote_address) ,
44 m_notifying(false) ,
45 m_info_commands(info_commands) ,
46 m_config_commands(config_commands) ,
47 m_with_terminate(with_terminate)
48{
49 G_LOG_S( "GSmtp::AdminServerPeer: admin connection from " << peer_info.m_address.displayString() ) ;
50 m_client_ptr.deletedSignal().connect( G::Slot::slot(*this,&AdminServerPeer::clientDone) ) ;
51 // dont prompt here -- it confuses some clients
52}
53
55{
56 m_client_ptr.deletedSignal().disconnect() ; // fwiw
57}
58
59void GSmtp::AdminServerPeer::clientDone( const std::string & s )
60{
61 if( s.empty() )
62 sendLine( "OK" ) ;
63 else
64 sendLine( "error: " + s ) ;
65}
66
67void GSmtp::AdminServerPeer::onDelete( const std::string & reason )
68{
69 G_LOG_S( "GSmtp::AdminServerPeer: admin connection closed: " << reason << (reason.empty()?"":": ")
70 << peerAddress().second.displayString() ) ;
71}
72
73void GSmtp::AdminServerPeer::onSecure( const std::string & , const std::string & , const std::string & )
74{
75}
76
77bool GSmtp::AdminServerPeer::onReceive( const char * line_data , std::size_t line_size , std::size_t ,
78 std::size_t , char )
79{
80 std::string line( line_data , line_size ) ;
81 if( is(line,"flush") )
82 {
83 flush() ;
84 }
85 else if( is(line,"forward") )
86 {
87 forward() ;
88 }
89 else if( is(line,"help") )
90 {
91 help() ;
92 }
93 else if( is(line,"status") )
94 {
95 status() ;
96 }
97 else if( is(line,"notify") )
98 {
99 m_notifying = true ;
100 }
101 else if( is(line,"list") )
102 {
103 sendList( spooled() ) ;
104 }
105 else if( is(line,"failures") )
106 {
107 sendList( failures() ) ;
108 }
109 else if( is(line,"unfail-all") )
110 {
111 unfailAll() ;
112 sendLine( "" ) ;
113 }
114 else if( is(line,"pid") )
115 {
116 sendLine( G::Process::Id().str() ) ;
117 }
118 else if( is(line,"quit") )
119 {
120 throw GNet::Done() ;
121 }
122 else if( is(line,"terminate") && m_with_terminate )
123 {
124 G_LOG_S( "GSmtp::AdminServerPeer::onReceive: received a terminate command from "
125 << peerAddress().second.displayString() ) ;
128 }
129 else if( is(line,"info") && !m_info_commands.empty() )
130 {
131 std::string arg = argument( line ) ;
132 if( arg.empty() || !find(arg,m_info_commands).first )
133 sendLine( "usage: info {" + G::Str::join("|",G::Str::keySet(m_info_commands)) + "}" ) ;
134 else
135 sendLine( find(arg,m_info_commands).second ) ;
136 }
137 else if( is(line,"config") && !m_config_commands.empty() )
138 {
139 std::string arg = argument( line ) ;
140 if( arg.empty() )
141 sendLine( G::Str::join( eol() , m_config_commands , "=[" , "]" ) ) ;
142 else if( !find(arg,m_config_commands).first )
143 sendLine( "usage: config [{" + G::Str::join("|",G::Str::keySet(m_config_commands)) + "}]" ) ;
144 else
145 sendLine( find(arg,m_config_commands).second ) ;
146 }
147 else if( line.find_first_not_of(" \r\n\t") != std::string::npos )
148 {
149 sendLine( "error: unrecognised command" ) ;
150 }
151 else
152 {
153 sendLine( "" ) ;
154 }
155 return true ;
156}
157
158std::string GSmtp::AdminServerPeer::eol() const
159{
160 std::string eol = lineBuffer().eol() ;
161 return eol.empty() ? std::string("\r\n") : eol ;
162}
163
164bool GSmtp::AdminServerPeer::is( const std::string & line_in , const std::string & key )
165{
166 G::StringArray parts ;
167 G::Str::splitIntoTokens( line_in , parts , G::Str::ws() ) ;
168 return !parts.empty() && G::Str::imatch( parts.at(0) , key ) ;
169}
170
171std::string GSmtp::AdminServerPeer::argument( const std::string & line_in )
172{
173 G::StringArray parts ;
174 G::Str::splitIntoTokens( line_in , parts , G::Str::ws() ) ;
175 return parts.size() > 1U ? parts.at(1U) : std::string() ;
176}
177
178std::pair<bool,std::string> GSmtp::AdminServerPeer::find( const std::string & line , const G::StringMap & map )
179{
180 for( const auto & item : map )
181 {
182 if( is(line,item.first) )
183 return std::make_pair(true,item.second) ;
184 }
185 return std::make_pair(false,std::string()) ;
186}
187
188void GSmtp::AdminServerPeer::help()
189{
190 std::set<std::string> commands ;
191 commands.insert( "flush" ) ;
192 commands.insert( "forward" ) ;
193 commands.insert( "help" ) ;
194 commands.insert( "status" ) ;
195 commands.insert( "list" ) ;
196 commands.insert( "failures" ) ;
197 commands.insert( "unfail-all" ) ;
198 commands.insert( "notify" ) ;
199 commands.insert( "pid" ) ;
200 commands.insert( "quit" ) ;
201 if( !m_info_commands.empty() ) commands.insert( "info" ) ;
202 if( !m_config_commands.empty() ) commands.insert( "config" ) ;
203 if( m_with_terminate ) commands.insert( "terminate" ) ;
204 sendLine( std::string("commands: ") + G::Str::join(", ",commands) ) ;
205}
206
207void GSmtp::AdminServerPeer::flush()
208{
209 G_DEBUG( "GSmtp::AdminServerPeer: flush: \"" << m_remote_address << "\"" ) ;
210 if( m_client_ptr.busy() )
211 {
212 sendLine( "error: still working" ) ;
213 }
214 else if( m_remote_address.empty() )
215 {
216 sendLine( "error: no remote server configured: use --forward-to" ) ;
217 }
218 else if( m_server.store().empty() )
219 {
220 sendLine( "error: no messages to send" ) ;
221 }
222 else
223 {
224 m_client_ptr.reset( std::make_unique<GSmtp::Client>( GNet::ExceptionSink(m_client_ptr,m_es.esrc()) ,
225 m_server.ff() , GNet::Location(m_remote_address) , m_server.clientSecrets() , m_server.clientConfig() ) ) ;
226
227 m_client_ptr->sendMessagesFrom( m_server.store() ) ; // once connected
228 // no sendLine() -- sends "OK" or "error:" when complete -- see AdminServerPeer::clientDone()
229 }
230}
231
232void GSmtp::AdminServerPeer::forward()
233{
234 if( m_remote_address.empty() )
235 {
236 sendLine( "error: no remote server configured: use --forward-to" ) ;
237 }
238 else
239 {
240 m_server.forward() ;
241 sendLine( "OK" ) ;
242 }
243}
244
245void GSmtp::AdminServerPeer::sendLine( std::string line , bool with_prompt )
246{
247 if( !line.empty() )
248 line.append( "\n" ) ;
249 G::Str::replaceAll( line , "\n" , eol() ) ;
250 if( with_prompt )
251 line.append( m_prompt ) ;
252 send_( line ) ;
253}
254
255void GSmtp::AdminServerPeer::notify( const std::string & s0 , const std::string & s1 ,
256 const std::string & s2 , const std::string &s3 )
257{
258 if( m_notifying )
259 {
260 std::string s = eol() + "EVENT: " + G::Str::printable(G::Str::join(": ",s0,s1,s2,s3)) ;
261 G::Str::unique( s , ' ' , ' ' ) ;
262 s.append( 2U , ' ' ) ;
263 send_( s ) ;
264 }
265}
266
267void GSmtp::AdminServerPeer::send_( const std::string & s )
268{
269 if( m_blocked )
270 {
271 G_DEBUG( "GSmtp::AdminServerPeer::send: flow control asserted: cannot send" ) ;
272 // could do better
273 }
274 else
275 {
276 m_blocked = ! send( s ) ; // GNet::SocketProtocol
277 }
278}
279
280void GSmtp::AdminServerPeer::onSendComplete()
281{
282 m_blocked = false ;
283}
284
285void GSmtp::AdminServerPeer::status()
286{
287 std::ostringstream ss ;
289 {
290 std::string eolstr = eol() ;
291 GNet::Monitor::instance()->report( ss , "" , eolstr ) ;
292 std::string report = ss.str() ;
293 G::Str::trimRight( report , {eolstr.data(),eolstr.size()} ) ;
294 sendLine( report ) ;
295 }
296 else
297 {
298 sendLine( "no info" ) ;
299 }
300}
301
302std::shared_ptr<GSmtp::MessageStore::Iterator> GSmtp::AdminServerPeer::spooled()
303{
304 return m_server.store().iterator(false) ;
305}
306
307std::shared_ptr<GSmtp::MessageStore::Iterator> GSmtp::AdminServerPeer::failures()
308{
309 return m_server.store().failures() ;
310}
311
312void GSmtp::AdminServerPeer::sendList( std::shared_ptr<MessageStore::Iterator> iter )
313{
314 std::ostringstream ss ;
315 for( bool first = true ;; first = false )
316 {
317 std::unique_ptr<StoredMessage> message( ++iter ) ;
318 if( message == nullptr ) break ;
319 if( !first ) ss << eol() ;
320 ss << message->id().str() ;
321 }
322
323 std::string result = ss.str() ;
324 if( result.empty() )
325 sendLine( "<none>" ) ;
326 else
327 sendLine( ss.str() ) ;
328}
329
330void GSmtp::AdminServerPeer::unfailAll()
331{
332 return m_server.store().unfailAll() ;
333}
334
336{
337 return m_notifying ;
338}
339
340// ===
341
343 FilterFactory & ff , G::Slot::Signal<const std::string&> & forward_request ,
344 const GNet::ServerPeerConfig & server_peer_config , const GNet::ServerConfig & server_config ,
345 const GSmtp::Client::Config & client_config , const GAuth::SaslClientSecrets & client_secrets ,
346 const G::StringArray & interfaces , unsigned int port , bool allow_remote ,
347 const std::string & remote_address , unsigned int connection_timeout ,
348 const G::StringMap & info_commands , const G::StringMap & config_commands ,
349 bool with_terminate ) :
350 GNet::MultiServer(es,interfaces,port,"admin",server_peer_config,server_config) ,
351 m_forward_timer(*this,&AdminServer::onForwardTimeout,es) ,
352 m_store(store) ,
353 m_ff(ff) ,
354 m_forward_request(forward_request) ,
355 m_client_config(client_config) ,
356 m_client_secrets(client_secrets) ,
357 m_allow_remote(allow_remote) ,
358 m_remote_address(remote_address) ,
359 m_connection_timeout(connection_timeout) ,
360 m_info_commands(info_commands) ,
361 m_config_commands(config_commands) ,
362 m_with_terminate(with_terminate)
363{
364}
365
367{
368 serverCleanup() ; // base class early cleanup
369}
370
371std::unique_ptr<GNet::ServerPeer> GSmtp::AdminServer::newPeer( GNet::ExceptionSinkUnbound esu ,
373{
374 std::unique_ptr<GNet::ServerPeer> ptr ;
375 try
376 {
377 std::string reason ;
378 if( !m_allow_remote && !GNet::Local::isLocal(peer_info.m_address,reason) )
379 {
380 G_WARNING( "GSmtp::Server: configured to reject non-local admin connection: " << reason ) ;
381 }
382 else
383 {
384 ptr = std::make_unique<AdminServerPeer>( esu , peer_info , *this , m_remote_address ,
385 m_info_commands , m_config_commands , m_with_terminate ) ; // up-cast
386 }
387 }
388 catch( std::exception & e ) // newPeer()
389 {
390 G_WARNING( "GSmtp::AdminServer: new connection error: " << e.what() ) ;
391 }
392 return ptr ;
393}
395{
396 // asychronous emit() for safety
397 m_forward_timer.startTimer( 0 ) ;
398}
399
400void GSmtp::AdminServer::onForwardTimeout()
401{
402 try
403 {
404 m_forward_request.emit( std::string("admin") ) ;
405 }
406 catch( std::exception & e )
407 {
408 G_WARNING( "GSmtp::AdminServer: exception: " << e.what() ) ;
409 }
410}
411
413{
414 serverReport() ;
415}
416
417void GSmtp::AdminServer::notify( const std::string & s0 , const std::string & s1 ,
418 const std::string & s2 , const std::string & s3 )
419{
420 if( hasPeers() )
421 {
422 using List = std::vector<std::weak_ptr<GNet::ServerPeer> > ;
423 List list = peers() ;
424 for( auto & wptr : list )
425 {
426 if( wptr.expired() ) continue ;
427 std::shared_ptr<GNet::ServerPeer> ptr = wptr.lock() ;
428 AdminServerPeer * peer = static_cast<AdminServerPeer*>( ptr.get() ) ; // downcast
429 G_DEBUG( "GSmtp::AdminServer::notify: " << peer << ": " << s0 << ": " << s1 ) ;
430 peer->notify( s0 , s1 , s2 , s3 ) ;
431 }
432 }
433}
434
436{
437 return m_store ;
438}
439
441{
442 return m_ff ;
443}
444
446{
447 return m_client_secrets ;
448}
449
451{
452 return m_connection_timeout ;
453}
454
456{
457 return m_client_config ;
458}
459
461{
462 bool result = false ;
463 if( hasPeers() )
464 {
465 using List = std::vector<std::weak_ptr<GNet::ServerPeer> > ;
466 List list = const_cast<AdminServer*>(this)->peers() ;
467 for( auto & wptr : list )
468 {
469 if( wptr.expired() ) continue ;
470 std::shared_ptr<GNet::ServerPeer> ptr = wptr.lock() ;
471 AdminServerPeer * peer = static_cast<AdminServerPeer*>( ptr.get() ) ; // downcast
472 if( peer->notifying() )
473 {
474 result = true ;
475 break ;
476 }
477 }
478 }
479 return result ;
480}
481
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret.
std::string displayString(bool with_scope_id=false) const
Returns a string which represents the transport address.
Definition: gaddress.cpp:375
G::Slot::Signal< const std::string & > & deletedSignal()
A signal that is triggered after deleteSignal() once the client has been deleted and the ClientPtr is...
Definition: gclientptr.cpp:32
An exception class that is detected by GNet::EventHandlerList and results in onException() being call...
Definition: gnetdone.h:40
static bool exists()
Returns true if an instance exists.
Definition: geventloop.cpp:54
virtual void quit(const std::string &reason)=0
Causes run() to return (once the call stack has unwound).
static EventLoop & instance()
Returns a reference to an instance of the class, if any.
Definition: geventloop.cpp:47
A potential ExceptionSink that is realised by bind()ing an exception source pointer.
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
static bool isLocal(const Address &, std::string &reason)
Returns true if the given address appears to be 'local', or a helpful error message if not.
Definition: glocal.cpp:64
A class that represents the remote target for out-going client connections.
Definition: glocation.h:71
void report(std::ostream &stream, const std::string &line_prefix=std::string(), const std::string &eol=std::string("\n")) const
Reports itself onto a stream.
Definition: gmonitor.cpp:163
static Monitor * instance()
Returns the singleton pointer. Returns nullptr if none.
Definition: gmonitor.cpp:98
A structure that GNet::Server uses to configure its ServerPeer objects.
Definition: gserverpeer.h:51
A structure used in GNet::Server::newPeer().
Definition: gserver.h:142
A derivation of ServerPeer for the administration interface.
Definition: gadminserver.h:53
bool notifying() const
Returns true if the remote user has asked for notifications.
void notify(const std::string &s0, const std::string &s1, const std::string &s2, const std::string &s4)
Called when something happens which the admin user might be interested in.
~AdminServerPeer() override
Destructor.
AdminServerPeer(GNet::ExceptionSinkUnbound, const GNet::ServerPeerInfo &, AdminServer &, const std::string &remote, const G::StringMap &info_commands, const G::StringMap &config_commands, bool with_terminate)
Constructor.
A server class which implements the emailrelay administration interface.
Definition: gadminserver.h:120
void report() const
Generates helpful diagnostics.
MessageStore & store()
Returns a reference to the message store, as passed in to the constructor.
AdminServer(GNet::ExceptionSink, MessageStore &store, FilterFactory &ff, G::Slot::Signal< const std::string & > &forward_request, const GNet::ServerPeerConfig &server_peer_config, const GNet::ServerConfig &server_config, const GSmtp::Client::Config &client_config, const GAuth::SaslClientSecrets &client_secrets, const G::StringArray &interfaces, unsigned int port, bool allow_remote, const std::string &remote_address, unsigned int connection_timeout, const G::StringMap &info_commands, const G::StringMap &config_commands, bool with_terminate)
Constructor.
void forward()
Called to trigger asynchronous forwarding.
FilterFactory & ff()
Returns a reference to the filter factory, as passed in to the constructor.
~AdminServer() override
Destructor.
GSmtp::Client::Config clientConfig() const
Returns the client configuration.
void notify(const std::string &s0, const std::string &s1, const std::string &s2, const std::string &s3)
Called when something happens which the admin users might be interested in.
unsigned int connectionTimeout() const
Returns the connection timeout, as passed in to the constructor.
const GAuth::SaslClientSecrets & clientSecrets() const
Returns a reference to the client secrets object, as passed in to the constructor.
std::unique_ptr< GNet::ServerPeer > newPeer(GNet::ExceptionSinkUnbound, GNet::ServerPeerInfo, GNet::MultiServer::ServerInfo) override
Override from GNet::MultiServer.
bool notifying() const
Returns true if the remote user has asked for notifications.
A factory interface for GSmtp::Filter message processors.
A class which allows SMTP messages to be stored and retrieved.
Definition: gmessagestore.h:73
Handles a connection from a remote SMTP client.
Definition: gsmtpserver.h:150
Process-id class.
Definition: gprocess.h:140
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1195
static bool imatch(char, char)
Returns true if the two characters are the same, ignoring Latin-1 case.
Definition: gstr.cpp:1414
static string_view ws()
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1255
static std::set< std::string > keySet(const StringMap &string_map)
Extracts the keys from a map of strings.
Definition: gstr.cpp:1238
static void splitIntoTokens(const std::string &in, StringArray &out, string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:1073
static std::string unique(const std::string &s, char c, char r)
Returns a string with repeated 'c' characters replaced by one 'r' character.
Definition: gstr.cpp:1472
static std::string & trimRight(std::string &s, string_view ws, std::size_t limit=0U)
Trims the rhs of s, taking off up to 'limit' of the 'ws' characters.
Definition: gstr.cpp:347
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:885
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'.
Definition: gstr.cpp:287
Network classes.
Definition: gdef.h:1115
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:201
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
std::map< std::string, std::string > StringMap
A std::map of std::strings.
Definition: gstrings.h:32
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:52
A configuration structure for GNet::Server.
Definition: gserver.h:50
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:57