E-MailRelay

gsmtpclientprotocol.h
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 gsmtpclientprotocol.h
19///
20
21#ifndef G_SMTP_CLIENT_PROTOCOL_H
22#define G_SMTP_CLIENT_PROTOCOL_H
23
24#include "gdef.h"
25#include "gmessagestore.h"
26#include "gstoredmessage.h"
27#include "gsaslclient.h"
28#include "gsecrets.h"
29#include "gslot.h"
30#include "gstrings.h"
31#include "gtimer.h"
32#include "gexception.h"
33#include <memory>
34#include <iostream>
35
36 namespace GSmtp
37{
38 class ClientProtocol ;
39 class ClientProtocolReply ;
40}
41
42//| \class GSmtp::ClientProtocolReply
43/// A private implementation class used by ClientProtocol.
44///
45 class GSmtp::ClientProtocolReply
46{
47 public:
48 enum class Type
49 {
50 PositivePreliminary = 1 ,
51 PositiveCompletion = 2 ,
52 PositiveIntermediate = 3 ,
53 TransientNegative = 4 ,
54 PermanentNegative = 5
55 } ;
56 enum class SubType
57 {
58 Syntax = 0 ,
59 Information = 1 ,
60 Connections = 2 ,
61 MailSystem = 3 ,
62 Invalid_SubType = 4
63 } ;
64 enum class Value
65 {
66 Internal_filter_ok = 222 ,
67 Internal_filter_abandon = 223 ,
68 Internal_secure = 224 ,
69 ServiceReady_220 = 220 ,
70 Ok_250 = 250 ,
71 Authenticated_235 = 235 ,
72 Challenge_334 = 334 ,
73 OkForData_354 = 354 ,
74 SyntaxError_500 = 500 ,
75 SyntaxError_501 = 501 ,
76 NotImplemented_502 = 502 ,
77 BadSequence_503 = 503 ,
78 Internal_filter_error = 590 ,
79 NotAuthenticated_535 = 535 ,
80 NotAvailable_454 = 454 ,
81 Invalid = 0
82 } ;
83
84 static ClientProtocolReply ok() ;
85 ///< Factory function for an ok reply.
86
87 static ClientProtocolReply ok( Value , const std::string & = std::string() ) ;
88 ///< Factory function for an ok reply with a specific 2xx value.
89
90 static ClientProtocolReply error( Value , const std::string & response , const std::string & error_reason ) ;
91 ///< Factory function for an error reply with a specific 5xx value.
92
93 explicit ClientProtocolReply( const std::string & line = std::string() ) ;
94 ///< Constructor for one line of text.
95
96 bool add( const ClientProtocolReply & other ) ;
97 ///< Adds more lines to this reply. Returns false if the
98 ///< numeric values are different.
99
100 bool incomplete() const ;
101 ///< Returns true if the reply is incomplete.
102
103 bool validFormat() const ;
104 ///< Returns true if a valid format.
105
106 bool positive() const ;
107 ///< Returns true if the numeric value of the
108 ///< reply is less than four hundred.
109
110 bool is( Value v ) const ;
111 ///< Returns true if the reply value is 'v'.
112
113 int value() const ;
114 ///< Returns the numeric value of the reply.
115
116 std::string text() const ;
117 ///< Returns the text of the reply, excluding the numeric part,
118 ///< and with embedded newlines.
119
120 std::string errorText() const ;
121 ///< Returns the text() string, plus any error reason, but with
122 ///< the guarantee that the returned string is empty if and only
123 ///< if the reply value is 2xx.
124
125 std::string errorReason() const ;
126 ///< Returns an error reason string, as passed to error().
127
128 bool textContains( std::string s ) const ;
129 ///< Returns true if the text() contains the given substring.
130
131 std::string textLine( const std::string & prefix ) const ;
132 ///< Returns a line of text() which starts with
133 ///< prefix.
134
135 Type type() const ;
136 ///< Returns the reply type (category).
137
138 SubType subType() const ;
139 ///< Returns the reply sub-type.
140
141 private:
142 static bool is_digit( char ) ;
143
144 private:
145 bool m_complete{false} ;
146 bool m_valid{false} ;
147 int m_value{0} ;
148 std::string m_text ;
149 std::string m_reason ; // additional error reason
150} ;
151
152//| \class GSmtp::ClientProtocol
153/// Implements the client-side SMTP protocol.
154///
155 class GSmtp::ClientProtocol : private GNet::TimerBase
156{
157 public:
158 G_EXCEPTION( NotReady , "not ready" ) ;
159 G_EXCEPTION( TlsError , "tls/ssl error" ) ;
160 G_EXCEPTION_CLASS( SmtpError , "smtp error" ) ;
161 using Reply = ClientProtocolReply ;
162
163 class Sender /// An interface used by ClientProtocol to send protocol messages.
164 {
165 public:
166 virtual bool protocolSend( const std::string & , std::size_t offset , bool go_secure ) = 0 ;
167 ///< Called by the Protocol class to send network data to
168 ///< the peer.
169 ///<
170 ///< The offset gives the location of the payload within the
171 ///< string buffer.
172 ///<
173 ///< Returns false if not all of the string was send due to
174 ///< flow control. In this case ClientProtocol::sendComplete() should
175 ///< be called as soon as the full string has been sent.
176 ///<
177 ///< Throws on error, eg. if disconnected.
178
179 virtual ~Sender() = default ;
180 ///< Destructor.
181 } ;
182
183 struct Config /// A structure containing GSmtp::ClientProtocol configuration parameters.
184 {
185 std::string thishost_name ; // EHLO parameter
186 unsigned int response_timeout{0U} ;
187 unsigned int ready_timeout{0U} ;
188 unsigned int filter_timeout{0U} ;
189 bool use_starttls_if_possible{false} ;
190 bool must_use_tls{false} ;
191 bool must_authenticate{false} ;
192 bool anonymous{false} ; // MAIL..AUTH=
193 bool must_accept_all_recipients{false} ;
194 bool eight_bit_strict{false} ; // fail 8bit messages to 7bit server
195 Config() ;
196 Config( const std::string & name , unsigned int response_timeout ,
197 unsigned int ready_timeout , unsigned int filter_timeout ,
198 bool use_starttls_if_possible , bool must_use_tls ,
199 bool must_authenticate , bool anonymous ,
200 bool must_accept_all_recipients , bool eight_bit_strict ) ;
201 Config & set_thishost_name( const std::string & ) ;
202 Config & set_response_timeout( unsigned int ) ;
203 Config & set_ready_timeout( unsigned int ) ;
204 Config & set_filter_timeout( unsigned int ) ;
205 Config & set_use_starttls_if_possible( bool = true ) ;
206 Config & set_must_use_tls( bool = true ) ;
207 Config & set_must_authenticate( bool = true ) ;
208 Config & set_anonymous( bool = true ) ;
209 Config & set_must_accept_all_recipients( bool = true ) ;
210 Config & set_eight_bit_strict( bool = true ) ;
211 } ;
212
213 ClientProtocol( GNet::ExceptionSink , Sender & sender ,
214 const GAuth::SaslClientSecrets & secrets , const std::string & sasl_client_config ,
215 const Config & config , bool in_secure_tunnel ) ;
216 ///< Constructor. The Sender interface is used to send protocol
217 ///< messages to the peer. The references are kept.
218
219 G::Slot::Signal<int,const std::string&,const std::string&,const G::StringArray&> & doneSignal() ;
220 ///< Returns a signal that is raised once the protocol has finished
221 ///< with a given message. The first signal parameter is the SMTP response
222 ///< value, or 0 for an internal non-SMTP error, or -1 for filter-abandon,
223 ///< or -2 for a filter-fail. The second parameter is the empty string on
224 ///< success or a non-empty response string. The third parameter contains
225 ///< any additional error reason text. The fourth parameter is a list
226 ///< of failed addressees (see 'must_accept_all_recipients').
227
228 G::Slot::Signal<> & filterSignal() ;
229 ///< Returns a signal that is raised when the protocol
230 ///< needs to do message filtering. The callee must call
231 ///< filterDone() when finished.
232
233 void start( std::weak_ptr<StoredMessage> ) ;
234 ///< Starts transmission of the given message. The doneSignal()
235 ///< is used to indicate that the message has been processed
236 ///< and the shared object should remain valid until then.
237 ///< Precondition: StoredMessage::toCount() != 0
238
239 void finish() ;
240 ///< Called after the last message has been sent. Sends a quit
241 ///< command and shuts down the socket.
242
243 void sendComplete() ;
244 ///< To be called when a blocked connection becomes unblocked.
245 ///< See ClientProtocol::Sender::protocolSend().
246
247 void filterDone( bool ok , const std::string & response , const std::string & reason ) ;
248 ///< To be called when the Filter interface has done its thing.
249 ///< If ok then the message processing continues; if not ok
250 ///< then the message processing fails with a done signal
251 ///< code of -1 if the response is empty, or -2.
252
253 void secure() ;
254 ///< To be called when the secure socket protocol has been
255 ///< successfully established.
256
257 bool apply( const std::string & rx ) ;
258 ///< Called on receipt of a line of text from the remote server.
259 ///< Returns true if the protocol is done and the doneSignal()
260 ///< has been emitted.
261
262 public:
263 ~ClientProtocol() override = default ;
264 ClientProtocol( const ClientProtocol & ) = delete ;
265 ClientProtocol( ClientProtocol && ) = delete ;
266 void operator=( const ClientProtocol & ) = delete ;
267 void operator=( ClientProtocol && ) = delete ;
268
269 private:
270 struct AuthError : public SmtpError
271 {
272 AuthError( const GAuth::SaslClient & , const ClientProtocolReply & ) ;
273 std::string str() const ;
274 } ;
275
276 private: // overrides
277 void onTimeout() override ; // Override from GNet::TimerBase.
278
279 private:
280 std::shared_ptr<StoredMessage> message() ;
281 void send( const char * ) ;
282 void send( const char * , const std::string & ) ;
283 void send( const char * , const std::string & , const std::string & ) ;
284 bool send( const std::string & , bool eot , bool sensitive = false ) ;
285 bool sendLine( std::string & ) ;
286 std::size_t sendLines() ;
287 void sendEhlo() ;
288 void sendHelo() ;
289 void sendMail() ;
290 void sendMailCore() ;
291 bool endOfContent() ;
292 bool applyEvent( const Reply & event , bool is_start_event = false ) ;
293 static bool parseReply( Reply & , const std::string & , std::string & ) ;
294 void raiseDoneSignal( int , const std::string & , const std::string & = std::string() ) ;
295 bool serverAuth( const ClientProtocolReply & reply ) const ;
296 G::StringArray serverAuthMechanisms( const ClientProtocolReply & reply ) const ;
297 void startFiltering() ;
298 static std::string initialResponse( const GAuth::SaslClient & ) ;
299
300 private:
301 enum class State {
302 sInit ,
303 sStarted ,
304 sServiceReady ,
305 sSentEhlo ,
306 sSentHelo ,
307 sAuth ,
308 sSentMail ,
309 sFiltering ,
310 sSentRcpt ,
311 sSentData ,
312 sSentDataStub ,
313 sData ,
314 sSentDot ,
315 sStartTls ,
316 sSentTlsEhlo ,
317 sMessageDone ,
318 sQuitting
319 } ;
320
321 private:
322 Sender & m_sender ;
323 const GAuth::SaslClientSecrets & m_secrets ;
324 std::unique_ptr<GAuth::SaslClient> m_sasl ;
325 std::weak_ptr<StoredMessage> m_message ;
326 Config m_config ;
327 State m_state ;
328 std::size_t m_to_index ;
329 std::size_t m_to_accepted ;
330 G::StringArray m_to_rejected ;
331 bool m_server_has_starttls ;
332 bool m_server_has_auth ;
333 bool m_server_secure ;
334 bool m_server_has_8bitmime ;
335 G::StringArray m_server_auth_mechanisms ;
336 bool m_authenticated_with_server ;
337 std::string m_auth_mechanism ;
338 bool m_in_secure_tunnel ;
339 bool m_warned ;
340 Reply m_reply ;
341 G::Slot::Signal<int,const std::string&,const std::string&,const G::StringArray&> m_done_signal ;
342 G::Slot::Signal<> m_filter_signal ;
343} ;
344
345 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_thishost_name( const std::string & s ) { thishost_name = s ; return *this ; }
346 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_response_timeout( unsigned int t ) { response_timeout = t ; return *this ; }
347 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_ready_timeout( unsigned int t ) { ready_timeout = t ; return *this ; }
348 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_filter_timeout( unsigned int t ) { filter_timeout = t ; return *this ; }
349 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_use_starttls_if_possible( bool b ) { use_starttls_if_possible = b ; return *this ; }
350 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_use_tls( bool b ) { must_use_tls = b ; return *this ; }
351 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_authenticate( bool b ) { must_authenticate = b ; return *this ; }
352 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_anonymous( bool b ) { anonymous = b ; return *this ; }
353 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_accept_all_recipients( bool b ) { must_accept_all_recipients = b ; return *this ; }
354 inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_eight_bit_strict( bool b ) { eight_bit_strict = b ; return *this ; }
355
356#endif
GAuth::SaslClientSecrets
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret.
Definition: gsaslclientsecrets.h:39
GAuth::SaslClient
A class that implements the client-side SASL challenge/response concept.
Definition: gsaslclient.h:41
GNet::ExceptionSink
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
Definition: gexceptionsink.h:100
GNet::TimerBase
An interface used by GNet::TimerList to keep track of pending timeouts and to deliver timeout events.
Definition: gtimer.h:41
GNet::TimerBase::t
G::TimerTime t() const
Used by TimerList to get the expiry epoch time.
Definition: gtimer.cpp:111
GSmtp::ClientProtocolReply
A private implementation class used by ClientProtocol.
Definition: gsmtpclientprotocol.h:46
GSmtp::ClientProtocolReply::text
std::string text() const
Returns the text of the reply, excluding the numeric part, and with embedded newlines.
Definition: gsmtpclientprotocol.cpp:745
GSmtp::ClientProtocolReply::is
bool is(Value v) const
Returns true if the reply value is 'v'.
Definition: gsmtpclientprotocol.cpp:729
GSmtp::ClientProtocolReply::textLine
std::string textLine(const std::string &prefix) const
Returns a line of text() which starts with prefix.
Definition: gsmtpclientprotocol.cpp:750
GSmtp::ClientProtocolReply::errorReason
std::string errorReason() const
Returns an error reason string, as passed to error().
Definition: gsmtpclientprotocol.cpp:740
GSmtp::ClientProtocolReply::textContains
bool textContains(std::string s) const
Returns true if the text() contains the given substring.
Definition: gsmtpclientprotocol.cpp:797
GSmtp::ClientProtocolReply::subType
SubType subType() const
Returns the reply sub-type.
Definition: gsmtpclientprotocol.cpp:776
GSmtp::ClientProtocolReply::add
bool add(const ClientProtocolReply &other)
Adds more lines to this reply.
Definition: gsmtpclientprotocol.cpp:786
GSmtp::ClientProtocolReply::type
Type type() const
Returns the reply type (category).
Definition: gsmtpclientprotocol.cpp:770
GSmtp::ClientProtocolReply::positive
bool positive() const
Returns true if the numeric value of the reply is less than four hundred.
Definition: gsmtpclientprotocol.cpp:719
GSmtp::ClientProtocolReply::value
int value() const
Returns the numeric value of the reply.
Definition: gsmtpclientprotocol.cpp:724
GSmtp::ClientProtocolReply::errorText
std::string errorText() const
Returns the text() string, plus any error reason, but with the guarantee that the returned string is ...
Definition: gsmtpclientprotocol.cpp:734
GSmtp::ClientProtocolReply::error
static ClientProtocolReply error(Value, const std::string &response, const std::string &error_reason)
Factory function for an error reply with a specific 5xx value.
Definition: gsmtpclientprotocol.cpp:699
GSmtp::ClientProtocolReply::incomplete
bool incomplete() const
Returns true if the reply is incomplete.
Definition: gsmtpclientprotocol.cpp:714
GSmtp::ClientProtocolReply::ClientProtocolReply
ClientProtocolReply(const std::string &line=std::string())
Constructor for one line of text.
Definition: gsmtpclientprotocol.cpp:663
GSmtp::ClientProtocolReply::ok
static ClientProtocolReply ok()
Factory function for an ok reply.
Definition: gsmtpclientprotocol.cpp:684
GSmtp::ClientProtocolReply::validFormat
bool validFormat() const
Returns true if a valid format.
Definition: gsmtpclientprotocol.cpp:709
GSmtp::ClientProtocol::Sender
An interface used by ClientProtocol to send protocol messages.
Definition: gsmtpclientprotocol.h:164
GSmtp::ClientProtocol::Sender::protocolSend
virtual bool protocolSend(const std::string &, std::size_t offset, bool go_secure)=0
Called by the Protocol class to send network data to the peer.
GSmtp::ClientProtocol::Sender::~Sender
virtual ~Sender()=default
Destructor.
GSmtp::ClientProtocol
Implements the client-side SMTP protocol.
Definition: gsmtpclientprotocol.h:156
GSmtp::ClientProtocol::filterSignal
G::Slot::Signal & filterSignal()
Returns a signal that is raised when the protocol needs to do message filtering.
Definition: gsmtpclientprotocol.cpp:656
GSmtp::ClientProtocol::doneSignal
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.
Definition: gsmtpclientprotocol.cpp:651
GSmtp::ClientProtocol::secure
void secure()
To be called when the secure socket protocol has been successfully established.
Definition: gsmtpclientprotocol.cpp:89
GSmtp::ClientProtocol::finish
void finish()
Called after the last message has been sent.
Definition: gsmtpclientprotocol.cpp:81
GSmtp::ClientProtocol::start
void start(std::weak_ptr< StoredMessage >)
Starts transmission of the given message.
Definition: gsmtpclientprotocol.cpp:57
GSmtp::ClientProtocol::ClientProtocol
ClientProtocol(GNet::ExceptionSink, Sender &sender, const GAuth::SaslClientSecrets &secrets, const std::string &sasl_client_config, const Config &config, bool in_secure_tunnel)
Constructor.
Definition: gsmtpclientprotocol.cpp:35
GSmtp::ClientProtocol::sendComplete
void sendComplete()
To be called when a blocked connection becomes unblocked.
Definition: gsmtpclientprotocol.cpp:95
GSmtp::ClientProtocol::apply
bool apply(const std::string &rx)
Called on receipt of a line of text from the remote server.
Definition: gsmtpclientprotocol.cpp:135
GSmtp::ClientProtocol::filterDone
void filterDone(bool ok, const std::string &response, const std::string &reason)
To be called when the Filter interface has done its thing.
Definition: gsmtpclientprotocol.cpp:541
gexception.h
gmessagestore.h
gsaslclient.h
gsecrets.h
gslot.h
gstoredmessage.h
gstrings.h
gtimer.h
GSmtp
SMTP and message-store classes.
Definition: gadminserver.h:39
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
GSmtp::ClientProtocol::Config
A structure containing GSmtp::ClientProtocol configuration parameters.
Definition: gsmtpclientprotocol.h:184
G::Slot::Signal< int, const std::string &, const std::string &, const G::StringArray & >