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