E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gadminserver.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2018 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 // gadminserver.cpp
19 //
20
21 #include "gdef.h"
22 #include "geventloop.h"
23 #include "gsmtp.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 <algorithm> // std::find()
33
34 GSmtp::AdminServerPeer::AdminServerPeer( GNet::Server::PeerInfo peer_info ,
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::BufferedServerPeer(peer_info,GNet::LineBufferConfig::autodetect()) ,
39 m_server(server) ,
40 m_prompt("E-MailRelay> ") ,
41 m_blocked(false) ,
42 m_remote_address(remote_address) ,
43 m_notifying(false) ,
44 m_info_commands(info_commands) ,
45 m_config_commands(config_commands) ,
46 m_with_terminate(with_terminate)
47 {
48 G_LOG_S( "GSmtp::AdminServerPeer: admin connection from " << peer_info.m_address.displayString() ) ;
49 m_client.doneSignal().connect( G::Slot::slot(*this,&AdminServerPeer::clientDone) ) ;
50 // dont prompt here -- it confuses some clients
51 }
52
53 GSmtp::AdminServerPeer::~AdminServerPeer()
54 {
55 // AdminServerPeer objects are destroyed from within the AdminServer::dtor body
56 // via the GNet::Server::serverCleanup() mechanism -- this allows this
57 // AdminServerPeer dtor to call the AdminServer safely
58
59 m_server.unregister( this ) ;
60 m_client.doneSignal().disconnect() ;
61 }
62
63 void GSmtp::AdminServerPeer::clientDone( std::string s )
64 {
65 m_client.reset() ;
66 if( s.empty() )
67 sendLine( "OK" ) ;
68 else
69 sendLine( std::string("error: ") + s ) ;
70 }
71
72 void GSmtp::AdminServerPeer::onDelete( const std::string & reason )
73 {
74 G_LOG_S( "GSmtp::AdminServerPeer: admin connection closed: " << reason << (reason.empty()?"":": ")
75 << peerAddress().second.displayString() ) ;
76 }
77
78 void GSmtp::AdminServerPeer::onSecure( const std::string & )
79 {
80 }
81
82 bool GSmtp::AdminServerPeer::onReceive( const char * line_data , size_t line_size , size_t )
83 {
84 std::string line( line_data , line_size ) ;
85 if( is(line,"flush") )
86 {
87 flush() ;
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 doDelete() ;
121 return false ;
122 }
123 else if( is(line,"terminate") && m_with_terminate )
124 {
125 if( GNet::EventLoop::exists() )
126 GNet::EventLoop::instance().quit("") ;
127 }
128 else if( is(line,"info") && !m_info_commands.empty() )
129 {
130 std::string arg = argument( line ) ;
131 if( arg.empty() || !find(arg,m_info_commands).first )
132 sendLine( "usage: info {" + G::Str::join("|",G::Str::keySet(m_info_commands)) + "}" ) ;
133 else
134 sendLine( find(arg,m_info_commands).second ) ;
135 }
136 else if( is(line,"config") && !m_config_commands.empty() )
137 {
138 std::string arg = argument( line ) ;
139 if( arg.empty() )
140 sendLine( G::Str::join( eol() , m_config_commands , "=[" , "]" ) ) ;
141 else if( !find(arg,m_config_commands).first )
142 sendLine( "usage: config [{" + G::Str::join("|",G::Str::keySet(m_config_commands)) + "}]" ) ;
143 else
144 sendLine( find(arg,m_config_commands).second ) ;
145 }
146 else if( line.find_first_not_of(" \r\n\t") != std::string::npos )
147 {
148 sendLine( "error: unrecognised command" ) ;
149 }
150 else
151 {
152 sendLine( "" ) ;
153 }
154 return true ;
155 }
156
157 std::string GSmtp::AdminServerPeer::eol() const
158 {
159 return lineBufferEndOfLine().empty() ? std::string("\r\n") : lineBufferEndOfLine() ;
160 }
161
162 bool GSmtp::AdminServerPeer::is( const std::string & line_in , const std::string & key )
163 {
164 G::StringArray parts ;
165 G::Str::splitIntoTokens( line_in , parts , G::Str::ws() ) ;
166 return parts.size() && G::Str::imatch( parts.at(0) , key ) ;
167 }
168
169 std::string GSmtp::AdminServerPeer::argument( const std::string & line_in )
170 {
171 G::StringArray parts ;
172 G::Str::splitIntoTokens( line_in , parts , G::Str::ws() ) ;
173 return parts.size() > 1U ? parts.at(1U) : std::string() ;
174 }
175
176 std::pair<bool,std::string> GSmtp::AdminServerPeer::find( const std::string & line , const G::StringMap & map )
177 {
178 for( G::StringMap::const_iterator p = map.begin() ; p != map.end() ; ++p )
179 {
180 if( is(line,(*p).first) )
181 return std::make_pair(true,(*p).second) ;
182 }
183 return std::make_pair(false,std::string()) ;
184 }
185
186 void GSmtp::AdminServerPeer::help()
187 {
188 std::set<std::string> commands ;
189 commands.insert( "flush" ) ;
190 commands.insert( "help" ) ;
191 commands.insert( "status" ) ;
192 commands.insert( "list" ) ;
193 commands.insert( "failures" ) ;
194 commands.insert( "unfail-all" ) ;
195 commands.insert( "notify" ) ;
196 commands.insert( "pid" ) ;
197 commands.insert( "quit" ) ;
198 if( !m_info_commands.empty() ) commands.insert( "info" ) ;
199 if( !m_config_commands.empty() ) commands.insert( "config" ) ;
200 if( m_with_terminate ) commands.insert( "terminate" ) ;
201 sendLine( std::string("commands: ") + G::Str::join(", ",commands) ) ;
202 }
203
204 void GSmtp::AdminServerPeer::flush()
205 {
206 G_DEBUG( "GSmtp::AdminServerPeer: flush: \"" << m_remote_address << "\"" ) ;
207 if( m_client.busy() )
208 {
209 sendLine( "error: still working" ) ;
210 }
211 else if( m_remote_address.empty() )
212 {
213 sendLine( "error: no remote server configured: use --forward-to" ) ;
214 }
215 else if( m_server.store().empty() )
216 {
217 sendLine( "error: no messages to send" ) ;
218 }
219 else
220 {
221 m_client.reset( new Client(GNet::Location(m_remote_address),m_server.secrets(),m_server.clientConfig()) ) ;
222 m_client->sendMessagesFrom( m_server.store() ) ; // once connected
223 // no sendLine() -- sends "OK" or "error:..." when complete
224 }
225 }
226
227 void GSmtp::AdminServerPeer::sendLine( std::string line , bool with_prompt )
228 {
229 if( !line.empty() )
230 line.append( "\n" ) ;
231 G::Str::replaceAll( line , "\n" , eol() ) ;
232 if( with_prompt )
233 line.append( m_prompt ) ;
234 send_( line ) ;
235 }
236
237 void GSmtp::AdminServerPeer::notify( const std::string & s0 , const std::string & s1 , const std::string & s2 )
238 {
239 if( m_notifying )
240 send_( eol() + "EVENT: " + G::Str::printable(G::Str::join(": ",s0,s1,s2)) ) ;
241 }
242
243 void GSmtp::AdminServerPeer::send_( const std::string & s )
244 {
245 if( m_blocked )
246 {
247 G_DEBUG( "GSmtp::AdminServerPeer::send: flow control asserted: cannot send" ) ;
248 // could do better
249 }
250 else
251 {
252 m_blocked = ! send( s ) ; // GNet::SocketProtocol
253 }
254 }
255
256 void GSmtp::AdminServerPeer::onSendComplete()
257 {
258 m_blocked = false ;
259 }
260
261 void GSmtp::AdminServerPeer::status()
262 {
263 std::ostringstream ss ;
264 if( GNet::Monitor::instance() )
265 {
266 GNet::Monitor::instance()->report( ss , "" , eol() ) ;
267 std::string report = ss.str() ;
268 G::Str::trimRight( report , eol() ) ;
269 sendLine( report ) ;
270 }
271 else
272 {
273 sendLine( "no info" ) ;
274 }
275 }
276
277 GSmtp::MessageStore::Iterator GSmtp::AdminServerPeer::spooled()
278 {
279 return m_server.store().iterator(false) ;
280 }
281
282 GSmtp::MessageStore::Iterator GSmtp::AdminServerPeer::failures()
283 {
284 return m_server.store().failures() ;
285 }
286
287 void GSmtp::AdminServerPeer::sendList( MessageStore::Iterator iter )
288 {
289 std::ostringstream ss ;
290 for( bool first = true ;; first = false )
291 {
292 unique_ptr<StoredMessage> message( iter.next() ) ;
293 if( message.get() == nullptr ) break ;
294 if( !first ) ss << eol() ;
295 ss << message->name() ;
296 }
297
298 std::string result = ss.str() ;
299 if( result.size() == 0U )
300 sendLine( "<none>" ) ;
301 else
302 sendLine( ss.str() ) ;
303 }
304
305 void GSmtp::AdminServerPeer::unfailAll()
306 {
307 return m_server.store().unfailAll() ;
308 }
309
310 // ===
311
312 GSmtp::AdminServer::AdminServer( GNet::ExceptionHandler & eh , MessageStore & store ,
313 const GSmtp::Client::Config & client_config , const GAuth::Secrets & secrets ,
314 const GNet::MultiServer::AddressList & listening_addresses , bool allow_remote ,
315 const std::string & remote_address , unsigned int connection_timeout ,
316 const G::StringMap & info_commands , const G::StringMap & config_commands ,
317 bool with_terminate ) :
318 GNet::MultiServer(eh,listening_addresses) ,
319 m_store(store) ,
320 m_client_config(client_config) ,
321 m_secrets(secrets) ,
322 m_allow_remote(allow_remote) ,
323 m_remote_address(remote_address) ,
324 m_connection_timeout(connection_timeout) ,
325 m_info_commands(info_commands) ,
326 m_config_commands(config_commands) ,
327 m_with_terminate(with_terminate)
328 {
329 }
330
331 GSmtp::AdminServer::~AdminServer()
332 {
333 // early cleanup so peers can call unregister() safely
334 serverCleanup() ; // GNet::MultiServer
335 }
336
337 GNet::ServerPeer * GSmtp::AdminServer::newPeer( GNet::Server::PeerInfo peer_info , GNet::MultiServer::ServerInfo )
338 {
339 try
340 {
341 std::string reason ;
342 if( ! m_allow_remote && ! GNet::Local::isLocal(peer_info.m_address,reason) )
343 {
344 G_WARNING( "GSmtp::Server: configured to reject non-local admin connection: " << reason ) ;
345 return nullptr ;
346 }
347
348 AdminServerPeer * peer = new AdminServerPeer( peer_info , *this , m_remote_address ,
349 m_info_commands , m_config_commands , m_with_terminate ) ;
350 m_peers.push_back( peer ) ;
351 return peer ;
352 }
353 catch( std::exception & e ) // newPeer()
354 {
355 G_WARNING( "GSmtp::AdminServer: exception from new connection: " << e.what() ) ;
356 return nullptr ;
357 }
358 }
359
360 void GSmtp::AdminServer::report() const
361 {
362 serverReport( "admin" ) ;
363 }
364
365 void GSmtp::AdminServer::notify( const std::string & s0 , const std::string & s1 , const std::string & s2 )
366 {
367 for( PeerList::iterator p = m_peers.begin() ; p != m_peers.end() ; ++p )
368 {
369 G_DEBUG( "GSmtp::AdminServer::notify: " << (*p) << ": " << s0 << ": " << s1 ) ;
370 (*p)->notify( s0 , s1 , s2 ) ;
371 }
372 }
373
374 void GSmtp::AdminServer::unregister( AdminServerPeer * peer )
375 {
376 G_DEBUG( "GSmtp::AdminServer::unregister: server=" << this << ": peer=" << peer ) ;
377 PeerList::iterator p = std::find( m_peers.begin() , m_peers.end() , peer ) ;
378 if( p != m_peers.end() )
379 m_peers.erase( p ) ;
380 }
381
382 GSmtp::MessageStore & GSmtp::AdminServer::store()
383 {
384 return m_store ;
385 }
386
387 const GAuth::Secrets & GSmtp::AdminServer::secrets() const
388 {
389 return m_secrets ;
390 }
391
392 unsigned int GSmtp::AdminServer::connectionTimeout() const
393 {
394 return m_connection_timeout ;
395 }
396
397 GSmtp::Client::Config GSmtp::AdminServer::clientConfig() const
398 {
399 return m_client_config ;
400 }
401
402 /// \file gadminserver.cpp
GNet::ServerPeer
An abstract base class for the GNet::Server's connection to a remote client.
Definition: gserver.h:173
G::Str::printable
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:701
GSmtp::AdminServer
A server class which implements the emailrelay administration interface.
Definition: gadminserver.h:112
gadminserver.h
GSmtp::AdminServerPeer::onSendComplete
virtual void onSendComplete() override
Override from GNet::BufferedServerPeer.
Definition: gadminserver.cpp:256
GNet
Network classes.
Definition: gaddress.h:29
GSmtp::AdminServer::notify
void notify(const std::string &s0, const std::string &s1, const std::string &s2)
Called when something happens which the admin user might be interested in.
Definition: gadminserver.cpp:365
GSmtp::AdminServer::unregister
void unregister(AdminServerPeer *)
Called from the AdminServerPeer destructor.
Definition: gadminserver.cpp:374
GSmtp::AdminServer::AdminServer
AdminServer(GNet::ExceptionHandler &, MessageStore &store, const GSmtp::Client::Config &client_config, const GAuth::Secrets &client_secrets, const GNet::MultiServer::AddressList &listening_addresses, 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.
Definition: gadminserver.cpp:312
GNet::Monitor::report
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:153
G::Str::imatch
static bool imatch(const std::string &, const std::string &)
Returns true if the two strings are the same, ignoring Latin-1 case.
Definition: gstr.cpp:1220
GSmtp::AdminServer::report
void report() const
Generates helpful diagnostics.
Definition: gadminserver.cpp:360
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
G::Str::splitIntoTokens
static void splitIntoTokens(const std::string &in, StringArray &out, const std::string &ws)
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:908
GSmtp::AdminServerPeer::onDelete
virtual void onDelete(const std::string &) override
Override from GNet::ServerPeer.
Definition: gadminserver.cpp:72
gslot.h
geventloop.h
GNet::ExceptionHandler
An abstract interface for handling exceptions thrown out of event-loop callbacks (socket events...
Definition: gexceptionhandler.h:44
GNet::Location
A class that holds a host/service name pair and the preferred address family (if any), and also the results of a name-to-address lookup, ie.
Definition: glocation.h:50
GSmtp::AdminServerPeer::AdminServerPeer
AdminServerPeer(GNet::Server::PeerInfo, AdminServer &, const std::string &remote, const G::StringMap &info_commands, const G::StringMap &config_commands, bool with_terminate)
Constructor.
Definition: gadminserver.cpp:34
gstr.h
GSmtp::MessageStore
A class which allows SMTP messages to be stored and retrieved.
Definition: gmessagestore.h:43
G::Str::replaceAll
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurances of sub-string 'from' with 'to'...
Definition: gstr.cpp:195
GAuth::Secrets
A simple interface for a store of secrets used in authentication.
Definition: gsecrets.h:40
G::Str::trimRight
static void trimRight(std::string &s, const std::string &ws, size_type limit=0U)
Trims the rhs of s, taking off up to 'limit' of the 'ws' characters.
Definition: gstr.cpp:235
GSmtp::AdminServer::secrets
const GAuth::Secrets & secrets() const
Returns a reference to the secrets object, as passed in to the constructor.
Definition: gadminserver.cpp:387
GNet::Monitor::instance
static Monitor * instance()
Returns the singleton pointer. Returns null if none.
Definition: gmonitor.cpp:113
G::Slot::slot
Slot0 slot(T &object, void(T::*fn)())
A slot factory function overloaded for a zero-parameter callback.
Definition: gslot.h:209
gprocess.h
gmessagestore.h
GNet::MultiServer::ServerInfo
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:100
GSmtp::AdminServer::store
MessageStore & store()
Returns a reference to the message store, as passed in to the constructor.
Definition: gadminserver.cpp:382
GSmtp::AdminServerPeer::~AdminServerPeer
virtual ~AdminServerPeer()
Destructor.
Definition: gadminserver.cpp:53
GSmtp::AdminServerPeer::notify
void notify(const std::string &s0, const std::string &s1, const std::string &s2)
Called when something happens.
Definition: gadminserver.cpp:237
GNet::Local::isLocal
static bool isLocal(const Address &, std::string &reason)
Returns true if the given address appears to be associated with the local host, or a helpful error me...
Definition: glocal.cpp:65
GSmtp::Client::Config
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:55
GSmtp::AdminServerPeer
A derivation of ServerPeer for the administration interface.
Definition: gadminserver.h:49
gmonitor.h
GSmtp::AdminServerPeer::onReceive
virtual bool onReceive(const char *, size_t, size_t) override
Override from GNet::BufferedServerPeer.
Definition: gadminserver.cpp:82
glocal.h
G::StringMap
std::map< std::string, std::string > StringMap
A std::map of std::strings.
Definition: gstrings.h:34
GSmtp::AdminServerPeer::onSecure
virtual void onSecure(const std::string &) override
Override from GNet::SocketProtocolSink.
Definition: gadminserver.cpp:78
G::Str::join
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1023
gstoredmessage.h
gsmtp.h
GSmtp::AdminServer::~AdminServer
virtual ~AdminServer()
Destructor.
Definition: gadminserver.cpp:331
GNet::EventLoop::exists
static bool exists()
Returns true if an instance exists.
Definition: geventloop.cpp:50
GSmtp::AdminServer::newPeer
virtual GNet::ServerPeer * newPeer(GNet::Server::PeerInfo, GNet::MultiServer::ServerInfo) g__final override
Override from GNet::MultiServer.
Definition: gadminserver.cpp:337
GSmtp::AdminServer::clientConfig
GSmtp::Client::Config clientConfig() const
Returns the client configuration.
Definition: gadminserver.cpp:397
G::Str::keySet
static std::set< std::string > keySet(const StringMap &string_map)
Extracts the keys from a map of strings.
Definition: gstr.cpp:1071
GNet::Server::PeerInfo
A structure used in GNet::Server::newPeer().
Definition: gserver.h:85
GSmtp::AdminServer::connectionTimeout
unsigned int connectionTimeout() const
Returns the connection timeout, as passed in to the constructor.
Definition: gadminserver.cpp:392
G::Str::ws
static std::string ws()
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1078
GNet::Address::displayString
std::string displayString() const
Returns a string which represents the transport address for debugging and diagnostics purposes...
Definition: gaddress_ipv4.cpp:129
GNet::EventLoop::instance
static EventLoop & instance()
Returns a reference to an instance of the class, if any.
Definition: geventloop.cpp:42
GNet::EventLoop::quit
virtual void quit(std::string reason)=0
Causes run() to return (once the call stack has unwound).
GSmtp::MessageStore::Iterator
An iterator class for GSmtp::MessageStore.
Definition: gmessagestore.h:55