E-MailRelay
gsmtpclient.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 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 "gfilterfactorybase.h"
30#include "gresolver.h"
31#include "gassert.h"
32#include "glog.h"
33#include <utility>
34
36 const GAuth::SaslClientSecrets & secrets , const Config & config ) :
37 GNet::Client(es,remote,netConfig(config)) ,
38 m_config(config) ,
39 m_nofilter_timer(*this,&Client::onNoFilterTimeout,es) ,
40 m_filter(ff.newFilter(es,Filter::Type::client,config.filter_config,config.filter_spec)) ,
41 m_protocol(es,*this,secrets,config.sasl_client_config,config.client_protocol_config,config.secure_tunnel) ,
42 m_secure(false) ,
43 m_filter_special(false)
44{
45 G_ASSERT( m_filter.get() != nullptr ) ;
46 m_protocol.doneSignal().connect( G::Slot::slot(*this,&Client::protocolDone) ) ;
47 m_protocol.filterSignal().connect( G::Slot::slot(*this,&Client::filterStart) ) ;
48 m_filter->doneSignal().connect( G::Slot::slot(*this,&Client::filterDone) ) ;
49}
50
52{
53 m_filter->doneSignal().disconnect() ;
54 m_protocol.filterSignal().disconnect() ;
55 m_protocol.doneSignal().disconnect() ;
56}
57
58GNet::Client::Config GSmtp::Client::netConfig( const Config & smtp_config )
59{
60 return
62 .set_stream_socket_config( smtp_config.stream_socket_config )
63 .set_line_buffer_config( GNet::LineBuffer::Config::smtp() )
64 .set_bind_local_address( smtp_config.bind_local_address )
65 .set_local_address( smtp_config.local_address )
66 .set_connection_timeout( smtp_config.connection_timeout )
67 .set_socket_protocol_config(
69 .set_client_tls_profile( smtp_config.client_tls_profile )
70 .set_secure_connection_timeout( smtp_config.secure_connection_timeout ) ) ;
71 //.set_response_timeout() // the protocol class does this
72 //.set_idle_timeout() // not needed
73}
74
76{
77 return m_message_done_signal ;
78}
79
80void GSmtp::Client::sendMessage( std::unique_ptr<GStore::StoredMessage> message )
81{
82 G_ASSERT( message.get() != nullptr ) ;
83 m_message = std::move( message ) ;
84 if( ready() )
85 start() ;
86}
87
88bool GSmtp::Client::ready() const
89{
90 return m_config.secure_tunnel ? ( connected() && m_secure ) : connected() ;
91}
92
93void GSmtp::Client::onConnect()
94{
95 if( m_config.secure_tunnel )
96 secureConnect() ; // GNet::SocketProtocol
97 else
98 start() ;
99}
100
101void GSmtp::Client::onSecure( const std::string & , const std::string & , const std::string & )
102{
103 m_secure = true ;
104 if( m_config.secure_tunnel )
105 start() ;
106 else
107 m_protocol.secure() ; // tell the protocol that STARTTLS is done
108}
109
110void GSmtp::Client::start()
111{
112 G_LOG_S( "GSmtp::Client::start: smtp connection to " << peerAddress().displayString() ) ;
113
114 G::CallFrame this_( m_stack ) ;
115 eventSignal().emit( "sending" , std::string(message()->id().str()) , std::string() ) ;
116 if( this_.deleted() ) return ;
117
118 m_protocol.start( std::weak_ptr<GStore::StoredMessage>(message()) ) ;
119}
120
121std::shared_ptr<GStore::StoredMessage> GSmtp::Client::message()
122{
123 G_ASSERT( m_message != nullptr ) ;
124 return m_message ;
125}
126
127bool GSmtp::Client::protocolSend( G::string_view line , std::size_t offset , bool go_secure )
128{
129 offset = std::min( offset , line.size() ) ;
130 G::string_view data( line.data()+offset , line.size()-offset ) ;
131 bool rc = data.empty() ? true : send( data ) ;
132 if( go_secure )
133 secureConnect() ; // GNet::Client -> GNet::SocketProtocol
134 return rc ;
135}
136
137void GSmtp::Client::filterStart()
138{
139 if( !message()->forwardTo().empty() )
140 {
141 // no client filter if "ForwardTo" is populated -- see GSmtp::Forward
142 m_nofilter_timer.startTimer( 0U ) ;
143 }
144 else
145 {
146 G_LOG_MORE( "GSmtp::Client::filterStart: client-filter [" << m_filter->id() << "]: [" << message()->id().str() << "]" ) ;
147 message()->close() ; // allow external editing
148 m_filter_special = false ;
149 m_filter->start( message()->id() ) ;
150 }
151}
152
153void GSmtp::Client::onNoFilterTimeout()
154{
155 m_protocol.filterDone( Filter::Result::ok , {} , {} ) ;
156}
157
158void GSmtp::Client::filterDone( int filter_result )
159{
160 G_ASSERT( static_cast<int>(m_filter->result()) == filter_result ) ;
161
162 const bool ok = filter_result == 0 ;
163 const bool abandon = filter_result == 1 ;
164 m_filter_special = m_filter->special() ;
165
166 G_LOG_IF( !m_filter->quiet() , "GSmtp::Client::filterDone: client-filter "
167 "[" << m_filter->id() << "]: [" << message()->id().str() << "]: "
168 << m_filter->str(Filter::Type::client) ) ;
169
170 std::string reopen_error ;
171 if( ok && !abandon )
172 reopen_error = message()->reopen() ;
173
174 // pass the event on to the client protocol
175 if( ok && reopen_error.empty() )
176 {
177 m_protocol.filterDone( Filter::Result::ok , {} , {} ) ;
178 }
179 else if( abandon )
180 {
181 m_protocol.filterDone( Filter::Result::abandon , {} , {} ) ; // protocolDone(-1)
182 }
183 else if( !reopen_error.empty() )
184 {
185 m_protocol.filterDone( Filter::Result::fail , "failed" , reopen_error ) ; // protocolDone(-2)
186 }
187 else
188 {
189 m_protocol.filterDone( Filter::Result::fail , m_filter->response() , m_filter->reason() ) ; // protocolDone(-2)
190 }
191}
192
193void GSmtp::Client::protocolDone( const ClientProtocol::DoneInfo & info )
194{
195 G_ASSERT( info.response_code >= -2 ) ;
196 G_DEBUG( "GSmtp::Client::protocolDone: \"" << info.response << "\"" ) ;
197
198 std::string reason = info.reason.empty() ? info.response : info.reason ;
199 std::string short_reason = ( info.response.empty() || info.reason.empty() ) ? info.response : info.reason ;
200 std::string message_id = message()->id().str() ;
201
202 if( info.response_code == -1 ) // filter abandon
203 {
204 // abandon this message if eg. already deleted
205 short_reason = "abandoned" ;
206 }
207 else if( info.response_code == -2 ) // filter error
208 {
209 messageFail( 550 , reason ) ;
210 short_reason = "rejected" ;
211 }
212 else if( info.response.empty() )
213 {
214 // forwarded ok to all, so delete our copy
215 messageDestroy() ;
216 }
217 else if( info.rejects.empty() )
218 {
219 // eg. rejected by the server, so fail the message
220 G_ASSERT( !reason.empty() ) ;
221 m_filter->cancel() ;
222 messageFail( info.response_code , reason ) ;
223 }
224 else
225 {
226 // some recipients rejected by the server, so update the to-list and fail the message
227 m_filter->cancel() ;
228 message()->editRecipients( info.rejects ) ;
229 messageFail( info.response_code , reason ) ;
230 }
231
232 G::CallFrame this_( m_stack ) ;
233 eventSignal().emit( "sent" , message_id , short_reason ) ;
234 if( this_.deleted() ) return ; // just in case
235
236 m_message.reset() ;
237 messageDoneSignal().emit( { std::max(0,info.response_code) , info.response , m_filter_special } ) ;
238}
239
241{
242 m_protocol.finish() ; // send QUIT
243 finish() ; // GNet::Client::finish() -- expect a disconnect
244}
245
246void GSmtp::Client::messageDestroy()
247{
248 message()->destroy() ;
249 m_message.reset() ;
250}
251
252void GSmtp::Client::messageFail( int response_code , const std::string & reason )
253{
254 message()->fail( reason , response_code ) ;
255 m_message.reset() ;
256}
257
258bool GSmtp::Client::onReceive( const char * line_data , std::size_t line_size , std::size_t , std::size_t , char )
259{
260 std::string line( line_data , line_size ) ;
261 G_DEBUG( "GSmtp::Client::onReceive: [" << G::Str::printable(line) << "]" ) ;
262
263 G::CallFrame this_( m_stack ) ;
264 bool done = m_protocol.apply( line ) ;
265 if( this_.deleted() ) return false ;
266
267 if( done )
268 {
269 m_message.reset() ;
270 quitAndFinish() ;
271 throw GNet::Done() ;
272 }
273 return !done ; // discard line-buffer input if done
274}
275
276void GSmtp::Client::onDelete( const std::string & error )
277{
278 G_DEBUG( "GSmtp::Client::onDelete: error [" << error << "]" ) ;
279 if( !error.empty() )
280 {
281 if( m_message && hasConnected() )
282 messageFail( 0 , error ) ; // if not already failed or destroyed
283 }
284 m_message.reset() ;
285}
286
287void GSmtp::Client::onSendComplete()
288{
289 m_protocol.sendComplete() ;
290}
291
292// ==
293
294GSmtp::Client::Config::Config() :
295 local_address(GNet::Address::defaultAddress())
296{
297}
298
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret.
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
G::Slot::Signal< const DoneInfo & > & doneSignal() noexcept
Returns a signal that is raised once the protocol has finished with a given message.
G::Slot::Signal & filterSignal() noexcept
Returns a signal that is raised when the protocol needs to do message filtering.
A class which acts as an SMTP client, sending messages to a remote SMTP server.
Definition: gsmtpclient.h:63
G::Slot::Signal< const MessageDoneInfo & > & messageDoneSignal() noexcept
Returns a signal that indicates that sendMessage() has completed or failed.
Definition: gsmtpclient.cpp:75
void quitAndFinish()
Finishes a sendMessage() sequence.
void sendMessage(std::unique_ptr< GStore::StoredMessage > message)
Starts sending the given message.
Definition: gsmtpclient.cpp:80
Client(GNet::ExceptionSink, FilterFactoryBase &, const GNet::Location &remote, const GAuth::SaslClientSecrets &, const Config &config)
Constructor.
Definition: gsmtpclient.cpp:35
~Client() override
Destructor.
Definition: gsmtpclient.cpp:51
A factory interface for making GSmtp::Filter message processors.
An interface for processing a message file through a filter.
Definition: gfilter.h:51
An object to represent a nested execution context.
Definition: gcall.h:88
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
A class like c++17's std::string_view.
Definition: gstringview.h:51
Network classes.
Definition: gdef.h:1144
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:240
A structure containing GNet::Client configuration parameters.
Definition: gclient.h:85
A configuration structure for GNet::SocketProtocol.
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:66