E-MailRelay
gsmtpclientprotocol.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 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 "gsmtpclientreply.h"
26#include "gmessagestore.h"
27#include "gstoredmessage.h"
28#include "gfilter.h"
29#include "gsaslclient.h"
30#include "gsaslclientsecrets.h"
31#include "gslot.h"
32#include "gstringarray.h"
33#include "gstringview.h"
34#include "glimits.h"
35#include "gtimer.h"
36#include "gexception.h"
37#include <vector>
38#include <memory>
39#include <iostream>
40
41namespace GSmtp
42{
43 class ClientProtocol ;
44}
45
46//| \class GSmtp::ClientProtocol
47/// Implements the client-side SMTP protocol.
48///
50{
51public:
52 G_EXCEPTION( NotReady , tx("not ready") )
53 G_EXCEPTION( TlsError , tx("tls/ssl error") )
54 G_EXCEPTION( BadSelector , tx("no client authentication account") )
55 G_EXCEPTION_CLASS( SmtpError , tx("smtp error") )
56
57 class Sender /// An interface used by ClientProtocol to send protocol messages.
58 {
59 public:
60 virtual bool protocolSend( std::string_view , std::size_t offset , bool go_secure ) = 0 ;
61 ///< Called by the Protocol class to send network data to
62 ///< the peer.
63 ///<
64 ///< The offset gives the location of the payload within the
65 ///< string-view.
66 ///<
67 ///< Returns false if not all of the string was sent due to
68 ///< flow control. In this case ClientProtocol::sendComplete() should
69 ///< be called as soon as the full string has been sent.
70 ///<
71 ///< Throws on error, eg. if disconnected.
72
73 virtual ~Sender() = default ;
74 ///< Destructor.
75 } ;
76
77 struct Config /// A structure containing GSmtp::ClientProtocol configuration parameters.
78 {
79 std::string ehlo ; // EHLO parameter
80 unsigned int response_timeout {0U} ;
81 unsigned int ready_timeout {0U} ;
82 bool use_starttls_if_possible {false} ;
83 bool must_use_tls {false} ;
84 bool authentication_fallthrough {false} ; // try MAIL FROM even if authentication failed
85 bool anonymous{false} ; // MAIL..AUTH=
86 bool must_accept_all_recipients {false} ;
87 bool eightbit_strict {false} ; // fail 8bit messages to non-8bitmime server
88 bool binarymime_strict {false} ; // fail binarymime messages to non-chunking server
89 bool smtputf8_strict {false} ; // fail utf8 mailbox names via non-smtputf8 server
90 bool pipelining {false} ; // send mail-to and all rcpt-to commands together
91 std::size_t reply_size_limit {G::Limits<>::net_buffer} ; // sanity check
92 std::size_t bdat_chunk_size {1000000} ; // n, TPDU size N=n+7+ndigits, ndigits=(int(log10(n))+1)
93 bool crlf_only {false} ; // CR-LF line endings, not as loose as RFC-2821 2.3.7
94 bool try_reauthentication {false} ; // try a new EHLO and AUTH if the client account changes
95 Config() ;
96 Config & set_ehlo( const std::string & ) ;
97 Config & set_response_timeout( unsigned int ) noexcept ;
98 Config & set_ready_timeout( unsigned int ) noexcept ;
99 Config & set_use_starttls_if_possible( bool = true ) noexcept ;
100 Config & set_must_use_tls( bool = true ) noexcept ;
101 Config & set_authentication_fallthrough( bool = true ) noexcept ;
102 Config & set_anonymous( bool = true ) noexcept ;
103 Config & set_must_accept_all_recipients( bool = true ) noexcept ;
104 Config & set_eightbit_strict( bool = true ) noexcept ;
105 Config & set_binarymime_strict( bool = true ) noexcept ;
106 Config & set_smtputf8_strict( bool = true ) noexcept ;
107 Config & set_pipelining( bool = true ) noexcept ;
108 Config & set_reply_size_limit( std::size_t ) noexcept ;
109 Config & set_crlf_only( bool = true ) noexcept ;
110 Config & set_try_reauthentication( bool = true ) noexcept ;
111 } ;
112
113 struct DoneInfo /// Parameters for GSmtp::ClientProtocol::doneSignal()
114 {
115 int response_code ; // smtp result code, or 0 for an internal non-smtp error, or -1 for filter-abandon, or -2 for a filter-fail
116 std::string response ; // response text, empty iff sent successfully
117 std::string reason ; // additional reason text (cf. GSmtp::Filter)
118 G::StringArray rejects ; // rejected RCPT-TO addresses
119 } ;
120
122 const GAuth::SaslClientSecrets & secrets , const std::string & sasl_client_config ,
123 const Config & config , bool in_secure_tunnel ) ;
124 ///< Constructor. The Sender interface is used to send protocol
125 ///< messages to the peer. The references are kept.
126
128 ///< Returns a signal that is raised once the protocol has finished
129 ///< with a given message.
130 ///<
131 ///< If 'must_accept_all_recipients' is false and the message was
132 ///< successfully sent to only some of the recipients then this is
133 ///< signalled as an error with a non-empty reject list.
134
135 G::Slot::Signal<> & filterSignal() noexcept ;
136 ///< Returns a signal that is raised when the protocol needs
137 ///< to do message filtering. The signal callee must call
138 ///< filterDone() when the filter has finished.
139
140 void reconfigure( const std::string & ehlo ) ;
141 ///< Updates a configuration parameter after construction.
142
143 void start( std::weak_ptr<GStore::StoredMessage> ) ;
144 ///< Starts transmission of the given message. The doneSignal()
145 ///< is used to indicate that the message has been processed
146 ///< and the shared object should remain valid until then.
147 ///<
148 ///< Precondition: GStore::StoredMessage::toCount() != 0
149
150 void finish() ;
151 ///< Called after the last message has been sent. Sends a quit
152 ///< command and shuts down the socket.
153
154 void sendComplete() ;
155 ///< To be called when a blocked connection becomes unblocked.
156 ///< See ClientProtocol::Sender::protocolSend().
157
158 void filterDone( Filter::Result result , const std::string & response , const std::string & reason ) ;
159 ///< To be called when the Filter interface has done its thing.
160 ///< If the result is Result::ok then the message processing
161 ///< continues; otherwise the message processing fails with
162 ///< a doneSignal() code of -1 for Result::abandon or -2 for
163 ///< Result::fail.
164
165 void secure() ;
166 ///< To be called when the secure socket protocol has been
167 ///< successfully established.
168
169 bool apply( const std::string & rx ) ;
170 ///< Called on receipt of a line of text from the remote server.
171 ///< Returns true if the protocol is done and the doneSignal()
172 ///< has been emitted.
173
174public:
175 ~ClientProtocol() override = default ;
176 ClientProtocol( const ClientProtocol & ) = delete ;
177 ClientProtocol( ClientProtocol && ) = delete ;
178 ClientProtocol & operator=( const ClientProtocol & ) = delete ;
179 ClientProtocol & operator=( ClientProtocol && ) = delete ;
180
181private: // overrides
182 void onTimeout() override ; // Override from GNet::TimerBase.
183
184private:
185 enum class State
186 {
187 Init ,
188 Started ,
189 ServiceReady ,
190 SentEhlo ,
191 SentHelo ,
192 Auth ,
193 SentMail ,
194 Filtering ,
195 SentRcpt ,
196 SentData ,
197 SentDataStub ,
198 SentBdatMore ,
199 SentBdatLast ,
200 Data ,
201 SentDot ,
202 StartTls ,
203 SentTlsEhlo ,
204 MessageDone ,
205 Quitting
206 } ;
207 struct ServerInfo
208 {
209 bool has_starttls {false} ;
210 bool has_auth {false} ;
211 bool secure {false} ;
212 bool has_8bitmime {false} ;
213 bool has_binarymime {false} ; // RFC-3030
214 bool has_chunking {false} ; // RFC-3030
215 bool has_pipelining {false} ;
216 bool has_smtputf8 {false} ;
217 G::StringArray auth_mechanisms ;
218 } ;
219 struct MessageState
220 {
221 std::weak_ptr<GStore::StoredMessage> ptr ;
222 std::string id ;
223 std::string selector ;
224 std::size_t content_size {0U} ;
225 std::size_t to_index {0U} ;
226 std::size_t to_accepted {0U} ; // count of accepted recipients
227 G::StringArray to_rejected ; // list of rejected recipients
228 std::size_t chunk_data_size {0U} ;
229 std::string chunk_data_size_str ;
230 } ;
231 struct SessionState
232 {
233 ServerInfo server ;
234 bool secure {false} ;
235 bool authenticated {false} ;
236 std::string client_ehlo ;
237 std::string auth_selector ;
238 std::string auth_mechanism ;
239 bool ok( const std::string & s ) const
240 {
241 return !authenticated || auth_selector == s ;
242 }
243 } ;
244 struct Protocol
245 {
246 State state {State::Init} ;
247 G::StringArray reply_lines ;
248 std::size_t replySize() const ;
249 } ;
250
251private:
252 using BodyType = GStore::MessageStore::BodyType ;
253 GStore::StoredMessage & message() ;
254 std::string_view checkSendable() ;
255 bool endOfContent() ;
256 bool applyEvent( const ClientReply & event ) ;
257 void raiseDoneSignal( int , const std::string & , const std::string & = {} ) ;
258 void startFiltering() ;
259 static GAuth::SaslClient::Response initialResponse( const GAuth::SaslClient & , std::string_view ) ;
260 //
261 void sendEot() ;
262 void sendCommandLines( const std::string & ) ;
263 void sendRsp( const GAuth::SaslClient::Response & ) ;
264 void send( std::string_view ) ;
265 void send( std::string_view , std::string_view , std::string_view = {} , std::string_view = {} , bool = false ) ;
266 std::size_t sendContentLines() ;
267 bool sendNextContentLine( std::string & ) ;
268 void sendEhlo() ;
269 void sendHelo() ;
270 bool sendMailFrom() ;
271 void sendRcptTo() ;
272 bool sendBdatAndChunk( std::size_t , const std::string & , bool ) ;
273 //
274 bool sendContentLineImp( const std::string & , std::size_t ) ;
275 void sendChunkImp( const char * , std::size_t ) ;
276 bool sendImp( std::string_view , std::size_t sensitive_from = std::string::npos ) ;
277
278private:
279 Sender & m_sender ;
280 std::unique_ptr<GAuth::SaslClient> m_sasl ;
281 Config m_config ;
282 const bool m_in_secure_tunnel ;
283 bool m_eightbit_warned {false} ;
284 bool m_binarymime_warned {false} ;
285 bool m_utf8_warned {false} ;
287 G::Slot::Signal<> m_filter_signal ;
288 Protocol m_protocol ;
289 MessageState m_message_state ;
290 GStore::StoredMessage * m_message_p {nullptr} ;
291 std::vector<char> m_message_buffer ;
292 std::string m_message_line ;
293 SessionState m_session ;
294} ;
295
296inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_ehlo( const std::string & s ) { ehlo = s ; return *this ; }
297inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_response_timeout( unsigned int t ) noexcept { response_timeout = t ; return *this ; }
298inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_ready_timeout( unsigned int t ) noexcept { ready_timeout = t ; return *this ; }
299inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_use_starttls_if_possible( bool b ) noexcept { use_starttls_if_possible = b ; return *this ; }
300inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_use_tls( bool b ) noexcept { must_use_tls = b ; return *this ; }
301inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_authentication_fallthrough( bool b ) noexcept { authentication_fallthrough = b ; return *this ; }
302inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_anonymous( bool b ) noexcept { anonymous = b ; return *this ; }
303inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_accept_all_recipients( bool b ) noexcept { must_accept_all_recipients = b ; return *this ; }
304inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_eightbit_strict( bool b ) noexcept { eightbit_strict = b ; return *this ; }
305inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_binarymime_strict( bool b ) noexcept { binarymime_strict = b ; return *this ; }
306inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_smtputf8_strict( bool b ) noexcept { smtputf8_strict = b ; return *this ; }
307inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_pipelining( bool b ) noexcept { pipelining = b ; return *this ; }
308inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_reply_size_limit( std::size_t n ) noexcept { reply_size_limit = n ; return *this ; }
309inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_crlf_only( bool b ) noexcept { crlf_only = b ; return *this ; }
310inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_try_reauthentication( bool b ) noexcept { try_reauthentication = b ; return *this ; }
311
312#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:42
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
An interface used by GNet::TimerList to keep track of pending timeouts and to deliver timeout events.
Definition: gtimer.h:42
G::TimerTime t() const
Used by TimerList to get the expiry epoch time.
Definition: gtimer.cpp:112
An interface used by ClientProtocol to send protocol messages.
virtual ~Sender()=default
Destructor.
virtual bool protocolSend(std::string_view, std::size_t offset, bool go_secure)=0
Called by the Protocol class to send network data to the peer.
Implements the client-side SMTP protocol.
ClientProtocol(GNet::EventState, Sender &sender, const GAuth::SaslClientSecrets &secrets, const std::string &sasl_client_config, const Config &config, bool in_secure_tunnel)
Constructor.
void start(std::weak_ptr< GStore::StoredMessage >)
Starts transmission of the given message.
void reconfigure(const std::string &ehlo)
Updates a configuration parameter after construction.
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 filterDone(Filter::Result result, const std::string &response, const std::string &reason)
To be called when the Filter interface has done its thing.
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.
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.
An interface for processing a message file through a filter.
Definition: gfilter.h:51
An abstract interface for messages which have come from the store.
SMTP classes.
Definition: gadminserver.h:42
Message store classes.
Definition: genvelope.cpp:30
Low-level classes.
Definition: garg.h:36
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
constexpr const char * tx(const char *p) noexcept
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
STL namespace.
Result structure returned from GAuth::SaslClient::response.
Definition: gsaslclient.h:45
A structure containing GSmtp::ClientProtocol configuration parameters.
Parameters for GSmtp::ClientProtocol::doneSignal()
A set of compile-time buffer sizes.
Definition: glimits.h:48
A slot holder, with connect() and emit() methods.
Definition: gslot.h:184