E-MailRelay
gadminserver_enabled.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 gadminserver_enabled.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 "gstringtoken.h"
32#include "gstr.h"
33#include "gstringarray.h"
34#include "glog.h"
35#include <utility>
36#include <limits>
37
38class GSmtp::AdminServerImp : public GNet::MultiServer
39{
40public:
41 AdminServerImp( GNet::EventState , GStore::MessageStore & store , FilterFactoryBase & ,
42 const GAuth::SaslClientSecrets & client_secrets , const G::StringArray & interfaces ,
43 const AdminServer::Config & config ) ;
44 ~AdminServerImp() override ;
46 void report( const std::string & group = {} ) const ;
47 GStore::MessageStore & store() ;
48 FilterFactoryBase & ff() ;
49 const GAuth::SaslClientSecrets & clientSecrets() const ;
50 GSmtp::Client::Config clientConfig() const ;
51 void emitCommand( AdminServer::Command , unsigned int ) ;
52 bool notifying() const ;
53 void notify( const std::string & s0 , const std::string & s1 , const std::string & s2 , const std::string & s3 ) ;
54
55public:
56 AdminServerImp( const AdminServerImp & ) = delete ;
57 AdminServerImp( AdminServerImp && ) = delete ;
58 AdminServerImp & operator=( const AdminServerImp & ) = delete ;
59 AdminServerImp & operator=( AdminServerImp && ) = delete ;
60
61protected:
62 std::unique_ptr<GNet::ServerPeer> newPeer( GNet::EventStateUnbound ,
64
65private:
66 void onCommandTimeout() ;
67
68private:
69 GStore::MessageStore & m_store ;
70 FilterFactoryBase & m_ff ;
71 const GAuth::SaslClientSecrets & m_client_secrets ;
72 AdminServer::Config m_config ;
73 GNet::Timer<AdminServerImp> m_command_timer ;
75 AdminServer::Command m_command {AdminServer::Command::forward} ;
76 unsigned int m_command_arg {0U} ;
77} ;
78
79// ==
80
82 AdminServerImp & server_imp , const std::string & remote_address ,
83 const G::StringMap & info_commands ,
84 bool with_terminate ) :
85 GNet::ServerPeer(esbind(esu,this),std::move(peer_info),GNet::LineBuffer::Config::autodetect()),
86 m_es(esbind(esu,this)) ,
87 m_server_imp(server_imp) ,
88 m_prompt("E-MailRelay> ") ,
89 m_remote_address(remote_address) ,
90 m_info_commands(info_commands) ,
91 m_with_terminate(with_terminate)
92{
93 G_LOG_S( "GSmtp::AdminServerPeer: admin connection from " << peerAddress().displayString() ) ;
94 m_client_ptr.deletedSignal().connect( G::Slot::slot(*this,&AdminServerPeer::clientDone) ) ;
95 // dont prompt here -- it confuses some clients
96}
97
99{
100 m_client_ptr.deletedSignal().disconnect() ;
101}
102
103void GSmtp::AdminServerPeer::clientDone( const std::string & s )
104{
105 G_DEBUG( "GSmtp::AdminServerPeer::clientDone: [" << s << "]" ) ;
106 if( s.empty() )
107 sendLine( "OK" ) ;
108 else
109 sendLine( std::move(std::string("error: ").append(s)) ) ;
110}
111
112void GSmtp::AdminServerPeer::onDelete( const std::string & reason )
113{
114 G_LOG_S( "GSmtp::AdminServerPeer: admin connection closed: " << reason << (reason.empty()?"":": ")
115 << peerAddress().displayString() ) ;
116}
117
118void GSmtp::AdminServerPeer::onSecure( const std::string & , const std::string & , const std::string & )
119{
120}
121
122bool GSmtp::AdminServerPeer::onReceive( const char * line_data , std::size_t line_size , std::size_t ,
123 std::size_t , char )
124{
125 std::string_view line( line_data , line_size ) ;
126 G::StringTokenView t( line , G::Str::ws() ) ;
127 if( is(t(),"flush") )
128 {
129 flush() ;
130 }
131 else if( is(t(),"forward") )
132 {
133 forward() ;
134 }
135 else if( is(t(),"help") )
136 {
137 help() ;
138 }
139 else if( is(t(),"status") )
140 {
141 status() ;
142 }
143 else if( is(t(),"notify") )
144 {
145 m_notifying = true ;
146 setIdleTimeout( 0U ) ; // GNet::ServerPeer
147 }
148 else if( is(t(),"list") )
149 {
150 sendMessageIds( m_server_imp.store().ids() ) ;
151 }
152 else if( is(t(),"failures") )
153 {
154 sendMessageIds( m_server_imp.store().failures() ) ;
155 }
156 else if( is(t(),"unfail-all") )
157 {
158 m_server_imp.store().unfailAll() ;
159 sendLine( std::string() ) ;
160 }
161 else if( is(t(),"pid") )
162 {
163 sendLine( G::Process::Id().str() ) ;
164 }
165 else if( is(t(),"quit") )
166 {
167 throw GNet::Done() ;
168 }
169 else if( is(t(),"terminate") && m_with_terminate )
170 {
171 G_LOG_S( "GSmtp::AdminServerPeer::onReceive: received a terminate command from "
172 << peerAddress().displayString() ) ;
175 }
176 else if( is(t(),"info") && !m_info_commands.empty() )
177 {
178 std::string_view arg = (++t)() ;
179 if( arg.empty() || !find(arg,m_info_commands).first )
180 sendLine( std::move(std::string("usage: info {").append(G::Str::join("|",G::Str::keys(m_info_commands))).append(1U,'}')) ) ;
181 else
182 sendLine( find(arg,m_info_commands).second ) ;
183 }
184 else if( is(t(),"dnsbl") )
185 {
186 std::string_view action = (++t)() ;
187 std::string_view arg = (++t)() ;
188 bool start = G::Str::imatch( action , "start" ) ;
189 if( ( start && arg.empty() ) || ( G::Str::imatch(action,"stop") && ( arg.empty() || G::Str::isUInt(arg) ) ) )
190 {
191 sendLine( "OK" ) ;
192 m_server_imp.emitCommand( AdminServer::Command::dnsbl , start ? 0U :
193 ( arg.empty() ? std::numeric_limits<unsigned int>::max() : G::Str::toUInt(arg,"0") ) ) ;
194 }
195 else
196 {
197 sendLine( "usage: dnsbl {start|stop <timeout>}" ) ;
198 }
199 }
200 else if( is(t(),"smtp") )
201 {
202 std::string_view arg = (++t)() ;
203 if( G::Str::imatch( arg , "disable" ) )
204 {
205 sendLine( "OK" ) ;
206 m_server_imp.emitCommand( AdminServer::Command::smtp_enable , 0U ) ;
207 }
208 else if( G::Str::imatch( arg , "enable" ) )
209 {
210 sendLine( "OK" ) ;
211 m_server_imp.emitCommand( AdminServer::Command::smtp_enable , 1U ) ;
212 }
213 else
214 {
215 sendLine( "usage: smtp {disable|enable}" ) ;
216 }
217 }
218 else if( line.find_first_not_of(" \r\n\t") != std::string::npos )
219 {
220 sendLine( "error: unrecognised command" ) ;
221 m_error_count++ ;
222 if( m_error_limit && m_error_count >= m_error_limit )
223 throw G::Exception( "too many errors" ) ;
224 }
225 else
226 {
227 sendLine( std::string() ) ;
228 }
229 return true ;
230}
231
232std::string GSmtp::AdminServerPeer::eol() const
233{
234 std::string eol = lineBuffer().eol() ;
235 return eol.empty() ? std::string("\r\n") : eol ;
236}
237
238bool GSmtp::AdminServerPeer::is( std::string_view token , std::string_view key )
239{
240 return G::Str::imatch( token , key ) ;
241}
242
243std::pair<bool,std::string> GSmtp::AdminServerPeer::find( std::string_view line , const G::StringMap & map )
244{
245 for( const auto & item : map )
246 {
247 if( is(line,item.first) )
248 return { true , item.second } ;
249 }
250 return { false , {} } ;
251}
252
253void GSmtp::AdminServerPeer::help()
254{
255 sendLine( std::move(std::string("commands: ")
256 .append( "dnsbl, " )
257 .append( "failures, " )
258 .append( "flush, " )
259 .append( "forward, " )
260 .append( "help, " )
261 .append( "info, " , m_info_commands.empty() ? 0U : 6U )
262 .append( "list, " )
263 .append( "notify, " )
264 .append( "pid, " )
265 .append( "quit, " )
266 .append( "smtp, " )
267 .append( "status, " )
268 .append( "terminate, " , m_with_terminate ? 11U : 0U )
269 .append( "unfail-all" )) ) ;
270}
271
272void GSmtp::AdminServerPeer::flush()
273{
274 G_DEBUG( "GSmtp::AdminServerPeer: flush: \"" << m_remote_address << "\"" ) ;
275 if( m_client_ptr.busy() )
276 {
277 sendLine( "error: still working" ) ;
278 }
279 else if( m_remote_address.empty() )
280 {
281 sendLine( "error: no remote server configured: use --forward-to" ) ;
282 }
283 else if( m_server_imp.store().empty() )
284 {
285 sendLine( "error: no messages to send" ) ;
286 }
287 else
288 {
289 m_client_ptr.reset( std::make_unique<GSmtp::Forward>( m_es.eh(m_client_ptr) ,
290 m_server_imp.store() , m_server_imp.ff() , GNet::Location(m_remote_address) ,
291 m_server_imp.clientSecrets() , m_server_imp.clientConfig() ) ) ;
292 // no sendLine() -- sends "OK" or "error:" when complete -- see AdminServerPeer::clientDone()
293 }
294}
295
296void GSmtp::AdminServerPeer::forward()
297{
298 if( m_remote_address.empty() )
299 {
300 sendLine( "error: no remote server configured: use --forward-to" ) ;
301 }
302 else
303 {
304 sendLine( "OK" ) ;
305 m_server_imp.emitCommand( AdminServer::Command::forward , 0U ) ;
306 }
307}
308
309void GSmtp::AdminServerPeer::sendLine( std::string && line ) // NOLINT cppcoreguidelines-rvalue-reference-param-not-moved
310{
311 if( !line.empty() )
312 line.append( "\n" ) ;
313 G::Str::replaceAll( line , "\n" , eol() ) ;
314 line.append( m_prompt ) ;
315 sendImp( line ) ;
316}
317
318void GSmtp::AdminServerPeer::notify( const std::string & s0 , const std::string & s1 , const std::string & s2 , const std::string & s3 )
319{
320 if( m_notifying )
321 {
322 std::string s = eol().append("EVENT: ").append(G::Str::printable(G::Str::join(": ",s0,s1,s2,s3))) ;
323 G::Str::unique( s , ' ' , ' ' ) ;
324 s.append( 2U , ' ' ) ;
325 sendImp( s ) ;
326 }
327}
328
329void GSmtp::AdminServerPeer::sendImp( const std::string & s )
330{
331 if( m_blocked )
332 {
333 G_DEBUG( "GSmtp::AdminServerPeer::send: flow control asserted: cannot send" ) ;
334 // could do better
335 }
336 else
337 {
338 m_blocked = !send( s ) ; // GNet::ServerPeer::send()
339 }
340}
341
342void GSmtp::AdminServerPeer::onSendComplete()
343{
344 m_blocked = false ;
345}
346
347void GSmtp::AdminServerPeer::status()
348{
349 std::ostringstream ss ;
351 {
352 const std::string eolstr = eol() ;
353 GNet::Monitor::instance()->report( ss , "" , eolstr ) ;
354 std::string report = ss.str() ;
355 G::Str::trimRight( report , eolstr ) ;
356 sendLine( std::move(report) ) ;
357 }
358 else
359 {
360 sendLine( "no info" ) ;
361 }
362}
363
364void GSmtp::AdminServerPeer::sendMessageIds( const std::vector<GStore::MessageId> & ids )
365{
366 std::ostringstream ss ;
367 bool first = true ;
368 for( const auto & id : ids )
369 {
370 if( !first ) ss << eol() ;
371 ss << id.str() ;
372 first = false ;
373 }
374
375 std::string result = ss.str() ;
376 if( result.empty() )
377 sendLine( "<none>" ) ;
378 else
379 sendLine( ss.str() ) ;
380}
381
383{
384 return m_notifying ;
385}
386
387// ==
388
390 FilterFactoryBase & ff , const GAuth::SaslClientSecrets & client_secrets ,
391 const G::StringArray & interfaces , const Config & config ) :
392 m_imp(std::make_unique<AdminServerImp>(es,store,ff,client_secrets,interfaces,config))
393{
394}
395
397= default ;
398
400{
401 return true ;
402}
403
405{
406 return m_imp->commandSignal() ;
407}
408
409void GSmtp::AdminServer::report( const std::string & group ) const
410{
411 m_imp->report( group ) ;
412}
413
414#ifndef G_LIB_SMALL
416{
417 return m_imp->store() ;
418}
419#endif
420
421#ifndef G_LIB_SMALL
423{
424 return m_imp->ff() ;
425}
426#endif
427
428#ifndef G_LIB_SMALL
430{
431 return m_imp->clientSecrets() ;
432}
433#endif
434
435#ifndef G_LIB_SMALL
436void GSmtp::AdminServer::emitCommand( Command command , unsigned int arg )
437{
438 m_imp->emitCommand( command , arg ) ;
439}
440#endif
441
443{
444 return m_imp->notifying() ;
445}
446
447void GSmtp::AdminServer::notify( const std::string & s0 , const std::string & s1 , const std::string & s2 , const std::string & s3 )
448{
449 m_imp->notify( s0 , s1 , s2 , s3 ) ;
450}
451
452// ==
453
454GSmtp::AdminServerImp::AdminServerImp( GNet::EventState es , GStore::MessageStore & store ,
455 FilterFactoryBase & ff , const GAuth::SaslClientSecrets & client_secrets ,
456 const G::StringArray & interfaces , const AdminServer::Config & config ) :
457 GNet::MultiServer(es,interfaces,config.port,"admin",config.net_server_peer_config,config.net_server_config) ,
458 m_store(store) ,
459 m_ff(ff) ,
460 m_client_secrets(client_secrets) ,
461 m_config(config) ,
462 m_command_timer(*this,&AdminServerImp::onCommandTimeout,es)
463{
464}
465
466GSmtp::AdminServerImp::~AdminServerImp()
467{
468 serverCleanup() ; // base class early cleanup
469}
470
471std::unique_ptr<GNet::ServerPeer> GSmtp::AdminServerImp::newPeer( GNet::EventStateUnbound esu ,
473{
474 std::unique_ptr<GNet::ServerPeer> ptr ;
475 try
476 {
477 std::string reason ;
478 if( !m_config.allow_remote && !peer_info.m_address.isLocal(reason) )
479 {
480 G_WARNING( "GSmtp::Server: configured to reject non-local admin connection: " << reason ) ;
481 }
482 else
483 {
484 ptr = std::make_unique<AdminServerPeer>( esu , std::move(peer_info) , *this ,
485 m_config.remote_address , m_config.info_commands ,
486 m_config.with_terminate ) ;
487 }
488 }
489 catch( std::exception & e ) // newPeer()
490 {
491 G_WARNING( "GSmtp::AdminServer: new connection error: " << e.what() ) ;
492 }
493 return ptr ;
494}
495
496void GSmtp::AdminServerImp::emitCommand( AdminServer::Command command , unsigned int arg )
497{
498 m_command = command ;
499 m_command_arg = arg ;
500 m_command_timer.startTimer( 0 ) ;
501}
502
503void GSmtp::AdminServerImp::onCommandTimeout()
504{
505 try
506 {
507 m_command_signal.emit( m_command , m_command_arg ) ;
508 }
509 catch( std::exception & e )
510 {
511 G_WARNING( "GSmtp::AdminServer: exception: " << e.what() ) ;
512 }
513}
514
515G::Slot::Signal<GSmtp::AdminServer::Command,unsigned int> & GSmtp::AdminServerImp::commandSignal() noexcept
516{
517 return m_command_signal ;
518}
519
520void GSmtp::AdminServerImp::report( const std::string & group ) const
521{
522 serverReport( group ) ;
523}
524
525void GSmtp::AdminServerImp::notify( const std::string & s0 , const std::string & s1 , const std::string & s2 , const std::string & s3 )
526{
527 if( hasPeers() )
528 {
529 using List = std::vector<std::weak_ptr<GNet::ServerPeer>> ;
530 List list = peers() ;
531 for( auto & wptr : list )
532 {
533 if( wptr.expired() ) continue ;
534 std::shared_ptr<GNet::ServerPeer> ptr = wptr.lock() ;
535 AdminServerPeer * peer = static_cast<AdminServerPeer*>( ptr.get() ) ; // downcast
536 peer->notify( s0 , s1 , s2 , s3 ) ;
537 }
538 }
539}
540
541GStore::MessageStore & GSmtp::AdminServerImp::store()
542{
543 return m_store ;
544}
545
546GSmtp::FilterFactoryBase & GSmtp::AdminServerImp::ff()
547{
548 return m_ff ;
549}
550
551const GAuth::SaslClientSecrets & GSmtp::AdminServerImp::clientSecrets() const
552{
553 return m_client_secrets ;
554}
555
556GSmtp::Client::Config GSmtp::AdminServerImp::clientConfig() const
557{
558 return m_config.smtp_client_config ;
559}
560
561bool GSmtp::AdminServerImp::notifying() const
562{
563 bool result = false ;
564 if( hasPeers() )
565 {
566 using List = std::vector<std::weak_ptr<GNet::ServerPeer>> ;
567 List list = const_cast<AdminServerImp*>(this)->peers() ;
568 for( auto & wptr : list )
569 {
570 if( wptr.expired() ) continue ;
571 std::shared_ptr<GNet::ServerPeer> ptr = wptr.lock() ;
572 AdminServerPeer * peer = static_cast<AdminServerPeer*>( ptr.get() ) ; // downcast
573 if( peer->notifying() )
574 {
575 result = true ;
576 break ;
577 }
578 }
579 }
580 return result ;
581}
582
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret.
G::Slot::Signal< const std::string & > & deletedSignal() noexcept
A signal that is triggered after deleteSignal() once the client has been deleted and the ClientPtr is...
Definition: gclientptr.cpp:27
An exception class that is caught separately by GNet::EventEmitter and GNet::TimerList so that onExce...
Definition: gnetdone.h:40
static bool exists()
Returns true if an instance exists.
Definition: geventloop.cpp:52
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:45
The EventStateUnbound class is used as a device to force factory methods to plumb-in an ExceptionSour...
Definition: geventstate.h:231
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
A class that represents the remote target for out-going client connections.
Definition: glocation.h:70
void report(std::ostream &stream, const std::string &line_prefix={}, const std::string &eol=std::string("\n")) const
Reports itself onto a stream.
Definition: gmonitor.cpp:159
static Monitor * instance()
Returns the singleton pointer. Returns nullptr if none.
Definition: gmonitor.cpp:94
A server that listens on more than one address using a facade pattern to multiple GNet::Server instan...
Definition: gmultiserver.h:48
virtual std::unique_ptr< ServerPeer > newPeer(EventStateUnbound, ServerPeerInfo &&, ServerInfo)=0
A factory method which creates a ServerPeer-derived object.
A move-only structure used in GNet::Server::newPeer() and containing the new socket.
Definition: gserver.h:142
Address peerAddress() const override
Returns the peer address.
A timer class template in which the timeout is delivered to the specified method.
Definition: gtimer.h:141
bool notifying() const
Returns true if the remote user has asked for notifications.
AdminServerPeer(GNet::EventStateUnbound, GNet::ServerPeerInfo &&, AdminServerImp &, const std::string &remote, const G::StringMap &info_commands, bool with_terminate)
Constructor.
~AdminServerPeer() override
Destructor.
void notify(const std::string &s0, const std::string &s1, const std::string &s2, const std::string &s3)
Called when something happens which the remote admin user might be interested in.
static bool enabled()
Returns true if the server is enabled.
~AdminServer()
Destructor.
G::Slot::Signal< Command, unsigned int > & commandSignal()
Returns a reference to a signal that is emit()ted when the remote user makes a request.
void report(const std::string &group={}) const
Generates helpful diagnostics.
AdminServer(GNet::EventState, GStore::MessageStore &store, FilterFactoryBase &, const GAuth::SaslClientSecrets &client_secrets, const G::StringArray &interfaces, const Config &config)
Constructor.
void emitCommand(Command, unsigned int)
Emits an asynchronous event on the commandSignal().
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.
GStore::MessageStore & store()
Returns a reference to the message store, as passed in to the constructor.
FilterFactoryBase & ff()
Returns a reference to the filter factory, 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.
bool notifying() const
Returns true if the remote user has asked for notifications.
A factory interface for making GSmtp::Filter message processors.
Handles a connection from a remote SMTP client.
Definition: gsmtpserver.h:176
A class which allows SMTP messages to be stored and retrieved.
Definition: gmessagestore.h:73
A general-purpose exception class derived from std::exception and containing an error message.
Definition: gexception.h:64
Process-id class.
Definition: gprocess.h:167
static std::string & trimRight(std::string &s, std::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:313
static bool isUInt(std::string_view s) noexcept
Returns true if the string can be converted into an unsigned integer without throwing an exception.
Definition: gstr.cpp:446
static std::string join(std::string_view sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1221
static StringArray keys(const StringMap &string_map)
Extracts the keys from a map of strings.
Definition: gstr.cpp:1256
static bool imatch(char, char) noexcept
Returns true if the two characters are the same, ignoring seven-bit case.
Definition: gstr.cpp:1415
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:1467
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
static unsigned int toUInt(std::string_view s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:648
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'.
Definition: gstr.cpp:247
static std::string_view ws() noexcept
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1265
A zero-copy string token iterator where the token separators are runs of whitespace characters,...
Definition: gstringtoken.h:54
Network classes.
Definition: gdef.h:1243
void report(const Server *, const std::string &group={})
Calls GPop::Server::report().
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:240
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
std::map< std::string, std::string > StringMap
A std::map of std::strings.
Definition: gstringmap.h:30
STL namespace.
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:55
A configuration structure for GNet::ServerPeer.
Definition: gserverpeer.h:64
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:67
A slot holder, with connect() and emit() methods.
Definition: gslot.h:184