E-MailRelay
gsmtpclientprotocol.h
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 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( G::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 thishost_name ; // 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_thishost_name( 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 start( std::weak_ptr<GStore::StoredMessage> ) ;
141 ///< Starts transmission of the given message. The doneSignal()
142 ///< is used to indicate that the message has been processed
143 ///< and the shared object should remain valid until then.
144 ///< Precondition: GStore::StoredMessage::toCount() != 0
145
146 void finish() ;
147 ///< Called after the last message has been sent. Sends a quit
148 ///< command and shuts down the socket.
149
150 void sendComplete() ;
151 ///< To be called when a blocked connection becomes unblocked.
152 ///< See ClientProtocol::Sender::protocolSend().
153
154 void filterDone( Filter::Result result , const std::string & response , const std::string & reason ) ;
155 ///< To be called when the Filter interface has done its thing.
156 ///< If the result is Result::ok then the message processing
157 ///< continues; otherwise the message processing fails with
158 ///< a doneSignal() code of -1 for Result::abandon or -2 for
159 ///< Result::fail.
160
161 void secure() ;
162 ///< To be called when the secure socket protocol has been
163 ///< successfully established.
164
165 bool apply( const std::string & rx ) ;
166 ///< Called on receipt of a line of text from the remote server.
167 ///< Returns true if the protocol is done and the doneSignal()
168 ///< has been emitted.
169
170public:
171 ~ClientProtocol() override = default ;
172 ClientProtocol( const ClientProtocol & ) = delete ;
173 ClientProtocol( ClientProtocol && ) = delete ;
174 ClientProtocol & operator=( const ClientProtocol & ) = delete ;
175 ClientProtocol & operator=( ClientProtocol && ) = delete ;
176
177private: // overrides
178 void onTimeout() override ; // Override from GNet::TimerBase.
179
180private:
181 enum class State
182 {
183 Init ,
184 Started ,
185 ServiceReady ,
186 SentEhlo ,
187 SentHelo ,
188 Auth ,
189 SentMail ,
190 Filtering ,
191 SentRcpt ,
192 SentData ,
193 SentDataStub ,
194 SentBdatMore ,
195 SentBdatLast ,
196 Data ,
197 SentDot ,
198 StartTls ,
199 SentTlsEhlo ,
200 MessageDone ,
201 Quitting
202 } ;
203 struct ServerInfo
204 {
205 bool has_starttls {false} ;
206 bool has_auth {false} ;
207 bool secure {false} ;
208 bool has_8bitmime {false} ;
209 bool has_binarymime {false} ; // RFC-3030
210 bool has_chunking {false} ; // RFC-3030
211 bool has_pipelining {false} ;
212 bool has_smtputf8 {false} ;
213 G::StringArray auth_mechanisms ;
214 } ;
215 struct MessageState
216 {
217 std::weak_ptr<GStore::StoredMessage> ptr ;
218 std::string id ;
219 std::string selector ;
220 std::size_t content_size {0U} ;
221 std::size_t to_index {0U} ;
222 std::size_t to_accepted {0U} ; // count of accepted recipients
223 G::StringArray to_rejected ; // list of rejected recipients
224 std::size_t chunk_data_size {0U} ;
225 std::string chunk_data_size_str ;
226 } ;
227 struct SessionState
228 {
229 ServerInfo server ;
230 bool secure {false} ;
231 bool authenticated {false} ;
232 std::string auth_selector ;
233 std::string auth_mechanism ;
234 bool ok( const std::string & s ) const
235 {
236 return !authenticated || auth_selector == s ;
237 }
238 } ;
239 struct Protocol
240 {
241 State state {State::Init} ;
242 G::StringArray reply_lines ;
243 std::size_t replySize() const ;
244 } ;
245
246private:
247 using BodyType = GStore::MessageStore::BodyType ;
248 GStore::StoredMessage & message() ;
249 std::string checkSendable() ;
250 bool endOfContent() ;
251 bool applyEvent( const ClientReply & event ) ;
252 void raiseDoneSignal( int , const std::string & , const std::string & = {} ) ;
253 void startFiltering() ;
254 static GAuth::SaslClient::Response initialResponse( const GAuth::SaslClient & , G::string_view ) ;
255 //
256 void sendEot() ;
257 void sendCommandLines( const std::string & ) ;
258 void sendRsp( const GAuth::SaslClient::Response & ) ;
259 void send( G::string_view ) ;
260 void send( G::string_view , G::string_view , G::string_view = {} , G::string_view = {} , bool = false ) ;
261 std::size_t sendContentLines() ;
262 bool sendNextContentLine( std::string & ) ;
263 void sendEhlo() ;
264 void sendHelo() ;
265 bool sendMailFrom() ;
266 void sendRcptTo() ;
267 bool sendBdatAndChunk( std::size_t , const std::string & , bool ) ;
268 //
269 bool sendContentLineImp( const std::string & , std::size_t ) ;
270 void sendChunkImp( const char * , std::size_t ) ;
271 bool sendImp( G::string_view , std::size_t sensitive_from = std::string::npos ) ;
272
273private:
274 Sender & m_sender ;
275 std::unique_ptr<GAuth::SaslClient> m_sasl ;
276 Config m_config ;
277 const bool m_in_secure_tunnel ;
278 bool m_eightbit_warned ;
279 bool m_binarymime_warned ;
280 bool m_utf8_warned ;
282 G::Slot::Signal<> m_filter_signal ;
283 Protocol m_protocol ;
284 MessageState m_message_state ;
285 GStore::StoredMessage * m_message_p {nullptr} ;
286 std::vector<char> m_message_buffer ;
287 std::string m_message_line ;
288 SessionState m_session ;
289} ;
290
291inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_thishost_name( const std::string & s ) { thishost_name = s ; return *this ; }
292inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_response_timeout( unsigned int t ) noexcept { response_timeout = t ; return *this ; }
293inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_ready_timeout( unsigned int t ) noexcept { ready_timeout = t ; return *this ; }
294inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_use_starttls_if_possible( bool b ) noexcept { use_starttls_if_possible = b ; return *this ; }
295inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_use_tls( bool b ) noexcept { must_use_tls = b ; return *this ; }
296inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_authentication_fallthrough( bool b ) noexcept { authentication_fallthrough = b ; return *this ; }
297inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_anonymous( bool b ) noexcept { anonymous = b ; return *this ; }
298inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_must_accept_all_recipients( bool b ) noexcept { must_accept_all_recipients = b ; return *this ; }
299inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_eightbit_strict( bool b ) noexcept { eightbit_strict = b ; return *this ; }
300inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_binarymime_strict( bool b ) noexcept { binarymime_strict = b ; return *this ; }
301inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_smtputf8_strict( bool b ) noexcept { smtputf8_strict = b ; return *this ; }
302inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_pipelining( bool b ) noexcept { pipelining = b ; return *this ; }
303inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_reply_size_limit( std::size_t n ) noexcept { reply_size_limit = n ; return *this ; }
304inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_crlf_only( bool b ) noexcept { crlf_only = b ; return *this ; }
305inline GSmtp::ClientProtocol::Config & GSmtp::ClientProtocol::Config::set_try_reauthentication( bool b ) noexcept { try_reauthentication = b ; return *this ; }
306
307#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 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:42
G::TimerTime t() const
Used by TimerList to get the expiry epoch time.
Definition: gtimer.cpp:114
An interface used by ClientProtocol to send protocol messages.
virtual bool protocolSend(G::string_view, 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.
void start(std::weak_ptr< GStore::StoredMessage >)
Starts transmission of the 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.
ClientProtocol(GNet::ExceptionSink, Sender &sender, const GAuth::SaslClientSecrets &secrets, const std::string &sasl_client_config, const Config &config, bool in_secure_tunnel)
Constructor.
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.
A class like c++17's std::string_view.
Definition: gstringview.h:51
SMTP classes.
Definition: gadminserver.h:42
Message store classes.
Definition: genvelope.cpp:30
Low-level classes.
Definition: garg.h:30
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
constexpr const char * tx(const char *p)
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