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