E-MailRelay
gsmtpforward.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 gsmtpforward.cpp
19///
20
21#include "gdef.h"
22#include "gsmtpforward.h"
24#include "gcall.h"
25#include "glog.h"
26#include <algorithm>
27#include <sstream>
28
30 FilterFactoryBase & ff , const GNet::Location & forward_to_default ,
31 const GAuth::SaslClientSecrets & secrets , const Config & config ) :
32 Forward( es , ff , forward_to_default , secrets , config )
33{
34 m_store = &store ; // NOLINT
35 m_iter = m_store->iterator( /*lock=*/true ) ;
36 m_continue_timer.startTimer( 0U ) ;
37}
38
40 FilterFactoryBase & ff , const GNet::Location & forward_to_default ,
41 const GAuth::SaslClientSecrets & secrets , const Config & config ) :
42 m_es(es) ,
43 m_store(nullptr) ,
44 m_ff(ff) ,
45 m_forward_to_default(forward_to_default) ,
46 m_forward_to_location(forward_to_default) ,
47 m_secrets(secrets) ,
48 m_config(config) ,
49 m_error_timer(*this,&Forward::onErrorTimeout,m_es) ,
50 m_continue_timer(*this,&Forward::onContinueTimeout,m_es) ,
51 m_message_count(0U) ,
52 m_has_connected(false) ,
53 m_finished(false)
54{
55 m_client_ptr.eventSignal().connect( G::Slot::slot(*this,&Forward::onEventSignal) ) ;
56 m_client_ptr.deleteSignal().connect( G::Slot::slot(*this,&Forward::onDeleteSignal) ) ;
57 m_client_ptr.deletedSignal().connect( G::Slot::slot(*this,&Forward::onDeletedSignal) ) ;
58}
59
61{
62 if( m_client_ptr.get() )
63 m_client_ptr->messageDoneSignal().disconnect() ;
64 if( m_routing_filter )
65 m_routing_filter->doneSignal().disconnect() ;
66 m_client_ptr.deletedSignal().disconnect() ;
67 m_client_ptr.deleteSignal().disconnect() ;
68 m_client_ptr.eventSignal().disconnect() ;
69}
70
71void GSmtp::Forward::onContinueTimeout()
72{
73 G_ASSERT( m_store != nullptr ) ;
74 if( !sendNext() )
75 {
76 quitAndFinish() ;
77 throw GNet::Done() ; // terminates us
78 }
79}
80
81bool GSmtp::Forward::sendNext()
82{
83 // start() the next message from the store, or return false if none
84 for(;;)
85 {
86 std::unique_ptr<GStore::StoredMessage> message( ++m_iter ) ;
87 if( message == nullptr )
88 break ;
89
90 // change the logging context asap to reflect the new message being forwarded
91 GNet::EventLoggingContext inner( m_es , Client::eventLoggingString(message.get(),m_config) ) ;
92
93 if( message->toCount() == 0U && m_config.fail_if_no_remote_recipients )
94 {
95 G_WARNING( "GSmtp::Forward::sendNext: forwarding [" << message->id().str() << "]: failing message with no remote recipients" ) ;
96 message->fail( "no remote recipients" , 0 ) ;
97 }
98 else if( message->toCount() == 0U )
99 {
100 G_DEBUG( "GSmtp::Forward::sendNext: forwarding [" << message->id().str() << "]: skipping message with no remote recipients" ) ;
101 }
102 else
103 {
104 G_LOG( "GSmtp::Forward::sendNext: forwarding [" << message->id().str() << "]" << messageInfo(*message) ) ;
105 start( std::move(message) ) ;
106 return true ;
107 }
108 }
109 if( m_message_count != 0U )
110 G_LOG( "GSmtp::Forward: forwarding: no more messages to send" ) ;
111 return false ;
112}
113
114void GSmtp::Forward::sendMessage( std::unique_ptr<GStore::StoredMessage> message )
115{
116 G_LOG( "GSmtp::Forward::sendMessage: forwarding [" << message->id().str() << "]" << messageInfo(*message) ) ;
117 start( std::move(message) ) ;
118}
119
120void GSmtp::Forward::start( std::unique_ptr<GStore::StoredMessage> message )
121{
122 m_message_count++ ;
123 if( !message->forwardTo().empty() )
124 {
125 message->close() ;
126 m_message = std::move( message ) ;
127
128 m_routing_filter = m_ff.newFilter( m_es , Filter::Type::routing , m_config.filter_config , m_config.filter_spec ) ;
129 G_LOG_MORE( "GSmtp::Forward::start: routing-filter [" << m_routing_filter->id() << "]: [" << m_message->id().str() << "]" ) ;
130 m_routing_filter->doneSignal().connect( G::Slot::slot(*this,&Forward::routingFilterDone) ) ;
131 m_routing_filter->start( m_message->id() ) ;
132 }
133 else if( updateClient( *message ) )
134 {
135 m_client_ptr->sendMessage( std::move(message) ) ;
136 }
137 else
138 {
139 m_continue_timer.startTimer( 0U ) ;
140 }
141}
142
143void GSmtp::Forward::routingFilterDone( int filter_result )
144{
145 G_ASSERT( m_routing_filter.get() != nullptr ) ;
146 G_ASSERT( m_message.get() != nullptr ) ;
147 G_ASSERT( static_cast<int>(m_routing_filter->result()) == filter_result ) ;
148 G_DEBUG( "GSmtp::Forward::routingFilterDone: result=" << filter_result ) ;
149
150 G_LOG_IF( !m_routing_filter->quiet() , "GSmtp::Forward::routingFilterDone: routing-filter "
151 "[" << m_routing_filter->id() << "]: [" << m_message->id().str() << "]: "
152 << m_routing_filter->str(Filter::Type::client) ) ;
153
154 const bool ok = filter_result == 0 && m_message ;
155 const bool abandon = filter_result == 1 ;
156 //const bool fail = filter_result == 2 ;
157
158 std::string reopen_error = ok ? m_message->reopen() : std::string() ;
159 bool continue_ = true ;
160 if( ok && reopen_error.empty() )
161 {
162 if( updateClient( *m_message ) )
163 {
164 continue_ = false ;
165 m_client_ptr->sendMessage( std::move(m_message) ) ;
166 }
167 }
168 else if( abandon )
169 {
170 // no-op
171 }
172 else
173 {
174 m_message->fail( "routing filter failed" , 0 ) ;
175 m_message.reset() ;
176 }
177
178 if( continue_ )
179 {
180 if( m_store == nullptr )
181 m_message_done_signal.emit( { 0 , abandon?"":"routing failed" , false } ) ;
182 else
183 m_continue_timer.startTimer( 0U ) ;
184 }
185}
186
187bool GSmtp::Forward::updateClient( const GStore::StoredMessage & message )
188{
189 bool new_address = m_forward_to_address != message.forwardToAddress() ;
190 bool new_selector = m_selector != message.clientAccountSelector() ;
191 if( unconnectable( message.forwardToAddress() ) )
192 {
193 G_LOG( "GSmtp::Forward::updateClient: forwarding [" << message.id().str() << "]: "
194 "skipping message with unconnectable address [" << message.forwardToAddress() << "]" ) ;
195 return false ;
196 }
197 else if( m_client_ptr.get() == nullptr )
198 {
199 G_DEBUG( "GSmtpForward::updateClient: new client [" << message.forwardToAddress() << "][" << message.clientAccountSelector() << "]" ) ;
200 newClient( message ) ;
201 }
202 else if( m_forward_to_address != message.forwardToAddress() ||
203 m_selector != message.clientAccountSelector() )
204 {
205 m_client_ptr->quitAndFinish() ;
206
207 std::ostringstream ss ;
208 if( new_address ) ss << "[" << message.forwardToAddress() << "]" ;
209 if( new_address && new_selector ) ss << " and " ;
210 if( new_selector ) ss << "account selector [" << message.clientAccountSelector() << "]" ;
211 G_LOG( "GSmtpForward::updateClient: forwarding [" << message.id().str() << "]: new connection for " << ss.str() ) ;
212
213 newClient( message ) ;
214 }
215 G_ASSERT( m_client_ptr.get() ) ;
216 return true ;
217}
218
219void GSmtp::Forward::newClient( const GStore::StoredMessage & message )
220{
221 m_has_connected = false ;
222 m_forward_to_address = message.forwardToAddress() ;
223 m_selector = message.clientAccountSelector() ;
224
225 if( m_client_ptr.get() )
226 m_client_ptr->messageDoneSignal().disconnect() ;
227
228 if( m_forward_to_address.empty() )
229 m_forward_to_location = m_forward_to_default ;
230 else
231 m_forward_to_location = GNet::Location( m_forward_to_address ) ;
232
233 m_client_ptr.reset( std::make_unique<GSmtp::Client>( m_es.eh(m_client_ptr) ,
234 m_ff , m_forward_to_location , m_secrets , m_config ) ) ;
235
236 m_client_ptr->messageDoneSignal().connect( G::Slot::slot(*this,&Forward::onMessageDoneSignal) ) ;
237}
238
240{
241 m_finished = true ;
242 if( m_client_ptr.get() )
243 m_client_ptr->quitAndFinish() ;
244}
245
246void GSmtp::Forward::onEventSignal( const std::string & p1 , const std::string & p2 , const std::string & p3 )
247{
248 m_event_signal.emit( std::string(p1) , std::string(p2) , std::string(p3) ) ;
249}
250
251void GSmtp::Forward::onDeleteSignal( const std::string & )
252{
253 // save the state of the Client before it goes away
254 G_ASSERT( m_client_ptr.get() ) ;
255 m_has_connected = m_client_ptr->hasConnected() ;
256}
257
258void GSmtp::Forward::onDeletedSignal( const std::string & reason )
259{
260 G_DEBUG( "GSmtp::Forward::onDeletedSignal: [" << reason << "]" ) ;
261 if( m_store && !m_has_connected && !m_forward_to_address.empty() )
262 {
263 // ignore connection failures to routed addresses -- just go on to the next message
264 G_ASSERT( !reason.empty() ) ; // GNet::Done only after connected
265 G_WARNING( "GSmtp::Forward::onDeletedSignal: smtp connection failed: " << reason ) ;
266 insert( m_unconnectable , m_forward_to_address ) ;
267 m_client_ptr.reset() ;
268 m_continue_timer.startTimer( 0U ) ;
269 }
270 else
271 {
272 // async throw
273 m_error = reason ;
274 m_error_timer.startTimer( 0U ) ;
275 }
276}
277
278void GSmtp::Forward::onErrorTimeout()
279{
280 throw G::Exception( m_error ) ; // terminates us
281}
282
283void GSmtp::Forward::onMessageDoneSignal( const Client::MessageDoneInfo & info )
284{
285 // optimise away repeated DNS queries on the default forward-to address
286 G_ASSERT( m_client_ptr.get() ) ;
287 if( m_client_ptr.get() && m_client_ptr->hasConnected() && m_forward_to_address.empty() &&
288 !m_forward_to_default.resolved() && m_client_ptr->remoteLocation().resolved() )
289 {
290 m_forward_to_default = m_client_ptr->remoteLocation() ;
291 }
292
293 if( m_store )
294 {
295 if( !sendNext() )
296 {
297 quitAndFinish() ;
298 throw GNet::Done() ; // terminates the client -- m_client_ptr calls onDeletedSignal()
299 }
300 }
301 else
302 {
303 m_message_done_signal.emit( info ) ;
304 }
305}
306
307void GSmtp::Forward::doOnDelete( const std::string & reason , bool /*done*/ )
308{
309 // (our owning ClientPtr is handling an exception by deleting us)
310 onDelete( reason ) ;
311}
312
313void GSmtp::Forward::onDelete( const std::string & reason )
314{
315 G_WARNING_IF( !reason.empty() , "GSmtp::Forward::onDelete: smtp client error: " << reason ) ;
316 if( m_message ) // if we own the message ie. while filtering
317 {
318 // fail the message, otherwise the dtor will just unlock it
319 G_ASSERT( !reason.empty() ) ; // filters dont throw GNet::Done
320 m_message->fail( reason , 0 ) ;
321 }
322}
323
325{
326 // (used for logging)
327 return m_client_ptr.get() ? m_client_ptr->peerAddressString() : std::string() ;
328}
329
331{
332 // (our owning ClientPtr treats exceptions as non-errors after quitAndFinish())
333 return m_finished ;
334}
335
336bool GSmtp::Forward::unconnectable( const std::string & forward_to ) const
337{
338 return !forward_to.empty() && contains( m_unconnectable , forward_to ) ;
339}
340
341void GSmtp::Forward::insert( G::StringArray & array , const std::string & value )
342{
343 G_ASSERT( !value.empty() ) ;
344 if( !contains( array , value ) )
345 array.insert( std::lower_bound(array.begin(),array.end(),value) , value ) ;
346}
347
348bool GSmtp::Forward::contains( const G::StringArray & array , const std::string & value )
349{
350 return !value.empty() && std::binary_search( array.begin() , array.end() , value ) ;
351}
352
353std::string GSmtp::Forward::messageInfo( const GStore::StoredMessage & message )
354{
355 std::ostringstream ss ;
356 if( !message.clientAccountSelector().empty() )
357 ss << " selector=[" << G::Str::printable(message.clientAccountSelector()) << "]" ;
358 if( !message.forwardTo().empty() )
359 ss << " forward-to=[" << G::Str::printable(message.forwardTo()) << "]" ;
360 if( !message.forwardToAddress().empty() )
361 ss << " forward-to-address=[" << G::Str::printable(message.forwardToAddress()) << "]" ;
362 return ss.str() ;
363}
364
366{
367 return m_message_done_signal ;
368}
369
371{
372 return m_event_signal ;
373}
374
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
G::Slot::Signal< const std::string & > & deleteSignal() noexcept
A signal that is triggered as the client is deleted following an exception handled by this class.
Definition: gclientptr.cpp:37
G::Slot::Signal< const std::string &, const std::string &, const std::string & > & eventSignal() noexcept
A signal that is linked to the contained client's eventSignal().
Definition: gclientptr.cpp:32
An exception class that is caught separately by GNet::EventEmitter and GNet::TimerList so that onExce...
Definition: gnetdone.h:40
A class that sets the G::LogOuput::context() while in scope.
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
A factory interface for making GSmtp::Filter message processors.
A class for forwarding messages from a message store that manages a GSmtp::Client instance,...
Definition: gsmtpforward.h:55
G::Slot::Signal< const std::string &, const std::string &, const std::string & > & eventSignal() noexcept
See GNet::Client::eventSignal()
Forward(GNet::EventState, GStore::MessageStore &store, FilterFactoryBase &, const GNet::Location &forward_to_default, const GAuth::SaslClientSecrets &, const Config &config)
Constructor.
std::string peerAddressString() const
Returns the Client's peerAddressString() if currently connected.
void doOnDelete(const std::string &reason, bool done)
Used by owning ClientPtr when handling an exception.
bool finished() const
Returns true after quitAndFinish().
virtual ~Forward()
Destructor.
G::Slot::Signal< const Client::MessageDoneInfo & > & messageDoneSignal() noexcept
Returns a signal that indicates that sendMessage() has completed or failed.
void quitAndFinish()
Finishes a sendMessage() sequence.
void sendMessage(std::unique_ptr< GStore::StoredMessage > message)
Starts sending the given message.
std::string str() const
Returns the id string.
A class which allows SMTP messages to be stored and retrieved.
Definition: gmessagestore.h:73
virtual std::unique_ptr< Iterator > iterator(bool lock)=0
Returns an iterator for stored messages.
An abstract interface for messages which have come from the store.
virtual std::string forwardToAddress() const =0
Returns the forwardTo() address or the empty string.
virtual std::string clientAccountSelector() const =0
Returns the client account selector or the empty string.
virtual MessageId id() const =0
Returns the message identifier.
virtual std::string forwardTo() const =0
Returns the routing override or the empty string.
A general-purpose exception class derived from std::exception and containing an error message.
Definition: gexception.h:64
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
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
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:67