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
36namespace GSmtp
37{
38 class ClientProtocol ;
39 class ClientProtocolReply ;
40}
41
42//| \class GSmtp::ClientProtocolReply
43/// A private implementation class used by ClientProtocol.
44///
46{
47public:
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
141private:
142 static bool is_digit( char ) ;
143
144private:
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///
156{
157public:
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
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
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
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
262public:
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
269private:
270 struct AuthError : public SmtpError
271 {
272 AuthError( const GAuth::SaslClient & , const ClientProtocolReply & ) ;
273 std::string str() const ;
274 } ;
275
276private: // overrides
277 void onTimeout() override ; // Override from GNet::TimerBase.
278
279private:
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
300private:
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
321private:
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 ;
342 G::Slot::Signal<> m_filter_signal ;
343} ;
344
345inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_thishost_name( const std::string & s ) { thishost_name = s ; return *this ; }
346inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_response_timeout( unsigned int t ) { response_timeout = t ; return *this ; }
347inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_ready_timeout( unsigned int t ) { ready_timeout = t ; return *this ; }
348inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_filter_timeout( unsigned int t ) { filter_timeout = t ; return *this ; }
349inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_use_starttls_if_possible( bool b ) { use_starttls_if_possible = b ; return *this ; }
350inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_use_tls( bool b ) { must_use_tls = b ; return *this ; }
351inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_authenticate( bool b ) { must_authenticate = b ; return *this ; }
352inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_anonymous( bool b ) { anonymous = b ; return *this ; }
353inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_accept_all_recipients( bool b ) { must_accept_all_recipients = b ; return *this ; }
354inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_eight_bit_strict( bool b ) { eight_bit_strict = b ; return *this ; }
355
356#endif
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret.
A class that implements the client-side SASL challenge/response concept.
Definition: gsaslclient.h:41
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
An interface used by GNet::TimerList to keep track of pending timeouts and to deliver timeout events.
Definition: gtimer.h:41
G::TimerTime t() const
Used by TimerList to get the expiry epoch time.
Definition: gtimer.cpp:111
A private implementation class used by ClientProtocol.
std::string text() const
Returns the text of the reply, excluding the numeric part, and with embedded newlines.
bool is(Value v) const
Returns true if the reply value is 'v'.
std::string textLine(const std::string &prefix) const
Returns a line of text() which starts with prefix.
std::string errorReason() const
Returns an error reason string, as passed to error().
bool textContains(std::string s) const
Returns true if the text() contains the given substring.
SubType subType() const
Returns the reply sub-type.
bool add(const ClientProtocolReply &other)
Adds more lines to this reply.
Type type() const
Returns the reply type (category).
bool positive() const
Returns true if the numeric value of the reply is less than four hundred.
int value() const
Returns the numeric value of the reply.
std::string errorText() const
Returns the text() string, plus any error reason, but with the guarantee that the returned string is ...
static ClientProtocolReply error(Value, const std::string &response, const std::string &error_reason)
Factory function for an error reply with a specific 5xx value.
bool incomplete() const
Returns true if the reply is incomplete.
ClientProtocolReply(const std::string &line=std::string())
Constructor for one line of text.
static ClientProtocolReply ok()
Factory function for an ok reply.
bool validFormat() const
Returns true if a valid format.
An interface used by ClientProtocol to send protocol messages.
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.
virtual ~Sender()=default
Destructor.
Implements the client-side SMTP protocol.
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.
void secure()
To be called when the secure socket protocol has been successfully established.
void finish()
Called after the last message has been sent.
void start(std::weak_ptr< StoredMessage >)
Starts transmission of the given message.
ClientProtocol(GNet::ExceptionSink, Sender &sender, const GAuth::SaslClientSecrets &secrets, const std::string &sasl_client_config, const Config &config, bool in_secure_tunnel)
Constructor.
void sendComplete()
To be called when a blocked connection becomes unblocked.
bool apply(const std::string &rx)
Called on receipt of a line of text from the remote server.
void filterDone(bool ok, const std::string &response, const std::string &reason)
To be called when the Filter interface has done its thing.
SMTP and message-store classes.
Definition: gadminserver.h:39
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
A structure containing GSmtp::ClientProtocol configuration parameters.