E-MailRelay
gsmtpclient.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 gsmtpclient.cpp
19///
20
21#include "gdef.h"
22#include "glocal.h"
23#include "gfile.h"
24#include "gstr.h"
25#include "gtimer.h"
26#include "gnetdone.h"
27#include "gsmtpclient.h"
28#include "gresolver.h"
29#include "gfilterfactory.h"
30#include "gresolver.h"
31#include "gassert.h"
32#include "glog.h"
33
35 const GAuth::SaslClientSecrets & client_secrets , const Config & config ) :
36 GNet::Client(es,remote,netConfig(config)) ,
37 m_store(nullptr) ,
38 m_filter(ff.newFilter(es,false,config.filter_address,config.filter_timeout)) ,
39 m_protocol(es,*this,client_secrets,config.sasl_client_config,config.client_protocol_config,config.secure_tunnel) ,
40 m_secure_tunnel(config.secure_tunnel) ,
41 m_message_count(0U)
42{
43 m_protocol.doneSignal().connect( G::Slot::slot(*this,&Client::protocolDone) ) ;
44 m_protocol.filterSignal().connect( G::Slot::slot(*this,&Client::filterStart) ) ;
45 m_filter->doneSignal().connect( G::Slot::slot(*this,&Client::filterDone) ) ;
46}
47
49{
50 m_protocol.doneSignal().disconnect() ;
51 m_protocol.filterSignal().disconnect() ;
52 m_filter->doneSignal().disconnect() ;
53}
54
55GNet::Client::Config GSmtp::Client::netConfig( const Config & smtp_config )
56{
58 net_config.bind_local_address = smtp_config.bind_local_address ;
59 net_config.local_address = smtp_config.local_address ;
60 net_config.connection_timeout = smtp_config.connection_timeout ;
61 net_config.secure_connection_timeout = smtp_config.secure_connection_timeout ;
62 //net_config.response_timeout = 0U ; // the protocol class does this
63 //net_config.idle_timeout = 0U ; // not needed
64 return net_config ;
65}
66
68{
69 return m_message_done_signal ;
70}
71
73{
74 G_ASSERT( m_store == nullptr ) ;
75 G_ASSERT( !connected() ) ; // ie. immediately after construction
76 m_store = &store ;
77}
78
79void GSmtp::Client::sendMessage( std::unique_ptr<StoredMessage> message )
80{
81 G_ASSERT( message && message->toCount() ) ;
82 if( message && message->toCount() )
83 {
84 m_message.reset( message.release() ) ;
85 if( connected() )
86 start() ;
87 }
88}
89
90void GSmtp::Client::onConnect()
91{
92 if( m_secure_tunnel )
93 secureConnect() ; // GNet::SocketProtocol
94 else
95 startSending() ;
96}
97
98void GSmtp::Client::onSecure( const std::string & , const std::string & , const std::string & )
99{
100 if( m_secure_tunnel )
101 startSending() ;
102 else
103 m_protocol.secure() ; // tell the protocol that STARTTLS is done
104}
105
106void GSmtp::Client::startSending()
107{
108 G_LOG_S( "GSmtp::Client::startSending: smtp connection to " << peerAddress().second.displayString() ) ;
109 if( m_store != nullptr )
110 {
111 // initialise the message iterator
112 m_iter = m_store->iterator( true ) ;
113
114 // start sending the first message
115 bool started = sendNext() ;
116 if( !started )
117 {
118 G_DEBUG( "GSmtp::Client::startSending: nothing to send" ) ;
119 quitAndFinish() ;
120 throw GNet::Done() ;
121 }
122 }
123 else
124 {
125 start() ;
126 }
127}
128
129bool GSmtp::Client::sendNext()
130{
131 m_message.reset() ;
132
133 // fetch the next message from the store, or return false if none
134 {
135 std::unique_ptr<StoredMessage> message( ++m_iter ) ;
136 if( message == nullptr )
137 {
138 if( m_message_count != 0U )
139 G_LOG( "GSmtp::Client: no more messages to send" ) ;
140 m_message_count = 0U ;
141 return false ;
142 }
143 m_message.reset( message.release() ) ;
144 }
145
146 start() ;
147 return true ;
148}
149
150void GSmtp::Client::start()
151{
152 G_ASSERT( message()->toCount() != 0U ) ;
153 m_message_count++ ;
154
155 G::CallFrame this_( m_stack ) ;
156 eventSignal().emit( "sending" , message()->location() , std::string() ) ;
157 if( this_.deleted() ) return ;
158
159 m_protocol.start( std::weak_ptr<StoredMessage>(message()) ) ;
160}
161
162std::shared_ptr<GSmtp::StoredMessage> GSmtp::Client::message()
163{
164 G_ASSERT( m_message != nullptr ) ;
165 if( m_message == nullptr )
166 m_message = std::make_shared<StoredMessageStub>() ;
167
168 return m_message ;
169}
170
171bool GSmtp::Client::protocolSend( const std::string & line , std::size_t offset , bool go_secure )
172{
173 bool rc = send( line , offset ) ; // GNet::Client::send()
174 if( go_secure )
175 secureConnect() ; // GNet::Client -> GNet::SocketProtocol
176 return rc ;
177}
178
179void GSmtp::Client::filterStart()
180{
181 if( !m_filter->simple() )
182 {
183 G_LOG( "GSmtp::Client::filterStart: client filter: [" << m_filter->id() << "]" ) ;
184 message()->close() ; // allow external editing
185 }
186 m_filter->start( message()->id() ) ;
187}
188
189void GSmtp::Client::filterDone( int filter_result )
190{
191 const bool ok = filter_result == 0 ;
192 const bool abandon = filter_result == 1 ;
193 const bool stop_scanning = m_filter->special() ;
194 G_ASSERT( m_filter->reason().empty() == (ok || abandon) ) ;
195
196 if( stop_scanning )
197 {
198 G_DEBUG( "GSmtp::Client::filterDone: making this the last message" ) ;
199 m_iter.reset() ; // so next iterNext() returns nothing
200 }
201
202 std::string reopen_error ;
203 if( !m_filter->simple() )
204 {
205 G_LOG( "GSmtp::Client::filterDone: client filter done: " << m_filter->str(false) ) ;
206 if( ok && !abandon )
207 reopen_error = message()->reopen() ;
208 }
209
210 // pass the event on to the client protocol
211 if( ok && reopen_error.empty() )
212 {
213 m_protocol.filterDone( true , std::string() , std::string() ) ;
214 }
215 else if( abandon )
216 {
217 m_protocol.filterDone( false , std::string() , std::string() ) ; // protocolDone(-1)
218 }
219 else if( !reopen_error.empty() )
220 {
221 m_protocol.filterDone( false , "failed" , reopen_error ) ; // protocolDone(-2)
222 }
223 else
224 {
225 m_protocol.filterDone( false , m_filter->response() , m_filter->reason() ) ; // protocolDone(-2)
226 }
227}
228
229void GSmtp::Client::protocolDone( int response_code , const std::string & response_in ,
230 const std::string & reason_in , const G::StringArray & rejectees )
231{
232 G_DEBUG( "GSmtp::Client::protocolDone: \"" << response_in << "\"" ) ;
233
234 std::string response = response_in.empty() ? std::string() : ( "smtp client failure: " + response_in ) ;
235 std::string reason = reason_in.empty() ? response : reason_in ;
236 std::string short_reason = ( response_in.empty() || reason_in.empty() ) ? response_in : reason_in ;
237 std::string message_location = message()->location() ;
238
239 if( response_code == -1 ) // filter abandon
240 {
241 // abandon this message if eg. already deleted
242 short_reason = "abandoned" ;
243 }
244 else if( response_code == -2 ) // filter error
245 {
246 messageFail( 550 , reason ) ;
247 short_reason = "rejected" ;
248 }
249 else if( response.empty() )
250 {
251 // forwarded ok to all, so delete our copy
252 messageDestroy() ;
253 }
254 else if( rejectees.empty() )
255 {
256 // eg. rejected by the server, so fail the message
257 G_ASSERT( !reason.empty() ) ;
258 m_filter->cancel() ;
259 messageFail( response_code , reason ) ;
260 }
261 else
262 {
263 // some recipients rejected by the server, so update the to-list and fail the message
264 m_filter->cancel() ;
265 message()->edit( rejectees ) ;
266 messageFail( response_code , reason ) ;
267 }
268
269 G::CallFrame this_( m_stack ) ;
270 eventSignal().emit( "sent" , message_location , short_reason ) ;
271 if( this_.deleted() ) return ; // just in case
272
273 if( m_store != nullptr )
274 {
275 if( !sendNext() )
276 {
277 G_DEBUG( "GSmtp::Client::protocolDone: all sent" ) ;
278 quitAndFinish() ;
279 throw GNet::Done() ;
280 }
281 }
282 else
283 {
284 messageDoneSignal().emit( response ) ;
285 }
286}
287
288void GSmtp::Client::quitAndFinish()
289{
290 m_protocol.finish() ; // send QUIT
291 finish( true ) ; // GNet::Client::finish() -- expect a disconnect
292}
293
294void GSmtp::Client::messageDestroy()
295{
296 message()->destroy() ;
297 m_message.reset() ;
298}
299
300void GSmtp::Client::messageFail( int response_code , const std::string & reason )
301{
302 message()->fail( reason , response_code ) ;
303 m_message.reset() ;
304}
305
306bool GSmtp::Client::onReceive( const char * line_data , std::size_t line_size , std::size_t , std::size_t , char )
307{
308 std::string line( line_data , line_size ) ;
309 G_DEBUG( "GSmtp::Client::onReceive: [" << G::Str::printable(line) << "]" ) ;
310
311 G::CallFrame this_( m_stack ) ;
312 bool done = m_protocol.apply( line ) ;
313 if( this_.deleted() ) return false ;
314
315 if( done )
316 {
317 quitAndFinish() ;
318 throw GNet::Done() ;
319 }
320 return !done ; // discard line-buffer input if done
321}
322
323void GSmtp::Client::onDelete( const std::string & error )
324{
325 G_DEBUG( "GSmtp::Client::onDelete: error [" << error << "]" ) ;
326 if( ! error.empty() )
327 {
328 G_LOG( "GSmtp::Client: smtp client error: " << error ) ;
329 if( m_message )
330 messageFail( 0 , error ) ; // if not already failed or destroyed
331 }
332 m_message.reset() ;
333}
334
335void GSmtp::Client::onSendComplete()
336{
337 m_protocol.sendComplete() ;
338}
339
340// ==
341
342GSmtp::Client::Config::Config() :
343 local_address(GNet::Address::defaultAddress())
344{
345}
346
347GSmtp::Client::Config::Config( const std::string & filter_address_ , unsigned int filter_timeout_ ,
348 bool bind_local_address_ , const GNet::Address & local_address_ ,
349 const ClientProtocol::Config & protocol_config_ , unsigned int connection_timeout_ ,
350 unsigned int secure_connection_timeout_ , bool secure_tunnel_ ,
351 const std::string & sasl_client_config_ ) :
352 filter_address(filter_address_) ,
353 filter_timeout(filter_timeout_) ,
354 bind_local_address(bind_local_address_) ,
355 local_address(local_address_) ,
356 client_protocol_config(protocol_config_) ,
357 connection_timeout(connection_timeout_) ,
358 secure_connection_timeout(secure_connection_timeout_) ,
359 secure_tunnel(secure_tunnel_) ,
360 sasl_client_config(sasl_client_config_)
361{
362}
363
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret.
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:53
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.
static LineBufferConfig smtp()
Convenience factory function.
A class that represents the remote target for out-going client connections.
Definition: glocation.h:71
G::Slot::Signal & filterSignal()
Returns a signal that is raised when the protocol needs to do message filtering.
G::Slot::Signal< int, const std::string &, const std::string &, const G::StringArray & > & doneSignal()
Returns a signal that is raised once the protocol has finished with a given message.
A class which acts as an SMTP client, extracting messages from a message store and forwarding them to...
Definition: gsmtpclient.h:54
void sendMessage(std::unique_ptr< StoredMessage > message)
Starts sending the given message.
Definition: gsmtpclient.cpp:79
void sendMessagesFrom(MessageStore &store)
Sends all messages from the given message store once connected.
Definition: gsmtpclient.cpp:72
~Client() override
Destructor.
Definition: gsmtpclient.cpp:48
Client(GNet::ExceptionSink, FilterFactory &, const GNet::Location &remote, const GAuth::SaslClientSecrets &client_secrets, const Config &config)
Constructor.
Definition: gsmtpclient.cpp:34
G::Slot::Signal< const std::string & > & messageDoneSignal()
Returns a signal that indicates that sendMessage() has completed or failed.
Definition: gsmtpclient.cpp:67
A factory interface for GSmtp::Filter message processors.
A class which allows SMTP messages to be stored and retrieved.
Definition: gmessagestore.h:73
An object to represent a nested execution context.
Definition: gcall.h:87
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:885
Network classes.
Definition: gdef.h:1115
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:201
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
A structure containing GNet::Client configuration parameters.
Definition: gclient.h:84
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:57