E-MailRelay
gsmtpserverprotocol.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 gsmtpserverprotocol.h
19///
20
21#ifndef G_SMTP_SERVER_PROTOCOL_H
22#define G_SMTP_SERVER_PROTOCOL_H
23
24#include "gdef.h"
25#include "gprotocolmessage.h"
26#include "gsmtpserverparser.h"
27#include "gsmtpserversender.h"
28#include "gsmtpserversend.h"
29#include "geventhandler.h"
30#include "gaddress.h"
31#include "gverifier.h"
32#include "gverifierstatus.h"
33#include "gsaslserver.h"
34#include "gsaslserversecrets.h"
35#include "gstatemachine.h"
36#include "glinebuffer.h"
37#include "gstringview.h"
38#include "gexception.h"
39#include "glimits.h"
40#include <utility>
41#include <memory>
42
43namespace GSmtp
44{
45 class ServerProtocol ;
46}
47
48//| \class GSmtp::ServerProtocol
49/// Implements the SMTP server-side protocol.
50///
51/// Uses the ProtocolMessage class as its down-stream interface, used for
52/// assembling and processing the incoming email messages.
53///
54/// Uses the ServerSender as its "sideways" interface to talk back to
55/// the client.
56///
57/// RFC-2920 PIPELINING suggests that reponses are batched while the protocol
58/// is working through a batch of incoming requests. Therefore pipelined
59/// requests should be apply()ed one by one with a parameter to indicate
60/// last-in-batch.
61///
62/// The return value from apply() will indicate whether a request has been
63/// fully processed. If the request is not immediately fully processed then
64/// the batch iteration must be paused until a response is emitted via
65/// protocolSend(). The GSmtp::ServerBufferIn class can help with this.
66///
67/// Some commands (DATA, NOOP, QUIT etc) should only appear at the end of
68/// a batch of pipelined requests and the responses to these commands
69/// should force any accumulated response batch to be flushed. (See also
70/// RFC-2920 3.2 (2) (5) (6) and RFC-3030 (chunking) 4.2.) If the caller
71/// implements response batching then the 'flush' parameter on the
72/// protocolSend() callback can be used to flush the batch.
73///
74/// Note that RCPT-TO commands are typically in the middle of a pipelined
75/// batch and might be processed asynchronously, but they do not cause the
76/// response batch to be flushed.
77///
79{
80public:
81 G_EXCEPTION_CLASS( Done , tx("smtp protocol done") ) ;
82 G_EXCEPTION( Busy , tx("smtp protocol busy") ) ;
83 using ApplyArgsTuple = std::tuple<const char *,std::size_t,std::size_t,std::size_t,char,bool> ; // see GNet::LineBuffer
84
85 class Text /// An interface used by GSmtp::ServerProtocol to provide response text strings.
86 {
87 public:
88 virtual std::string greeting() const = 0 ;
89 ///< Returns a system identifier for the initial greeting.
90
91 virtual std::string hello( const std::string & smtp_peer_name ) const = 0 ;
92 ///< Returns a hello response.
93
94 virtual std::string received( const std::string & smtp_peer_name , bool auth , bool secure ,
95 const std::string & protocol , const std::string & cipher ) const = 0 ;
96 ///< Returns a complete 'Received' line.
97
98 virtual ~Text() = default ;
99 ///< Destructor.
100 } ;
101
102 struct Config /// A configuration structure for GSmtp::ServerProtocol.
103 {
104 bool mail_requires_authentication {false} ; // for MAIL or VRFY, unless a trusted address
105 bool mail_requires_encryption {false} ;
106
107 bool with_vrfy {false} ;
108 bool with_chunking {true} ; // CHUNKING (BDAT) and also advertise BINARYMIME
109 bool with_pipelining {true} ;
110 bool with_smtputf8 {false} ;
111 bool smtputf8_strict {false} ; // reject non-ASCII characters if no MAIL-FROM SMTPUTF8 parameter
112
113 bool tls_starttls {false} ;
114 bool tls_connection {false} ; // smtps
115 int shutdown_how_on_quit {1} ;
116 unsigned int client_error_limit {8U} ;
117 std::size_t max_size {0U} ; // EHLO SIZE
118 std::string sasl_server_config ;
119 std::string sasl_server_challenge_hostname ; // eg. GNet::Local::canonicalName()
120
121 Config() ;
122 Config & set_mail_requires_authentication( bool = true ) noexcept ;
123 Config & set_mail_requires_encryption( bool = true ) noexcept ;
124 Config & set_with_vrfy( bool = true ) noexcept ;
125 Config & set_with_chunking( bool = true ) noexcept ;
126 Config & set_with_pipelining( bool = true ) noexcept ;
127 Config & set_with_smtputf8( bool = true ) noexcept ;
128 Config & set_smtputf8_strict( bool = true ) noexcept ;
129 Config & set_max_size( std::size_t ) noexcept ;
130 Config & set_tls_starttls( bool = true ) noexcept ;
131 Config & set_tls_connection( bool = true ) noexcept ;
132 Config & set_shutdown_how_on_quit( int ) noexcept ;
133 Config & set_client_error_limit( unsigned int ) noexcept ;
134 Config & set_sasl_server_config( const std::string & ) ;
135 Config & set_sasl_server_challenge_hostname( const std::string & ) ;
136 } ;
137
139 const GAuth::SaslServerSecrets & secrets , Text & text ,
140 const GNet::Address & peer_address , const Config & config ,
141 bool enabled ) ;
142 ///< Constructor.
143 ///<
144 ///< The ServerSender interface is used to send protocol responses
145 ///< back to the client.
146 ///<
147 ///< The Verifier interface is used to verify recipient
148 ///< addresses. See GSmtp::Verifier.
149 ///<
150 ///< The ProtocolMessage interface is used to assemble and
151 ///< process an incoming message.
152 ///<
153 ///< The Text interface is used to get informational text for
154 ///< returning to the client.
155
156 void setSender( ServerSender & ) ;
157 ///< Sets the ServerSender interface, overriding the constructor
158 ///< parameter.
159
160 void init() ;
161 ///< Starts the protocol. Use only once after construction.
162 ///< The implementation uses the ServerSender interface to either
163 ///< send the plaintext SMTP greeting or start the TLS
164 ///< handshake.
165
166 ~ServerProtocol() override ;
167 ///< Destructor.
168
169 bool inDataState() const ;
170 ///< Returns true if currently in a data-transfer state
171 ///< meaning that the next apply() does not need to
172 ///< contain a complete line of text. This is typically
173 ///< used to enable the GNet::LineBuffer 'fragments'
174 ///< option.
175
176 bool inBusyState() const ;
177 ///< Returns true if in a state where the protocol is
178 ///< waiting for an asynchronous filter of address-verifier
179 ///< to complete. A call to apply() will throw an exception
180 ///< when in this state.
181
182 bool apply( const ApplyArgsTuple & ) ;
183 ///< Called on receipt of a complete line of text from the
184 ///< client, or possibly a line fragment iff this object is
185 ///< currently inDataState().
186 ///<
187 ///< Throws an error if inBusyState().
188 ///<
189 ///< Returns false if the protocol is now inBusyState(); the
190 ///< caller should stop apply()ing any more data until the
191 ///< next ServerSender::protocolSend() callback.
192 ///<
193 ///< Throws Done at the end of the protocol.
194 ///<
195 ///< To allow for RFC-2920 PIPELINING the 'more' field
196 ///< should be set if there is another line that is ready to
197 ///< be apply()d. This defines an input batch and allows the
198 ///< ServerSender::protocolSend() callback to ask that the
199 ///< associated responses also get batched up on output.
200
201 void secure( const std::string & certificate , const std::string & protocol , const std::string & cipher ) ;
202 ///< To be called when the transport protocol successfully
203 ///< goes into secure mode. See ServerSender::protocolSend().
204
205 G::Slot::Signal<> & changeSignal() noexcept ;
206 ///< A signal that is emitted at the end of apply() or whenever
207 ///< the protocol state might have changed by some other
208 ///< mechanism (eg. GSmtp::Verifier).
209
210public:
211 ServerProtocol( const ServerProtocol & ) = delete ;
212 ServerProtocol( ServerProtocol && ) = delete ;
213 ServerProtocol & operator=( const ServerProtocol & ) = delete ;
214 ServerProtocol & operator=( ServerProtocol && ) = delete ;
215
216private:
217 enum class Event
218 {
219 Unknown ,
220 Quit ,
221 Helo ,
222 Ehlo ,
223 Rset ,
224 Noop ,
225 Expn ,
226 Data ,
227 DataFail ,
228 DataContent ,
229 Bdat ,
230 BdatLast ,
231 BdatLastZero ,
232 BdatCheck ,
233 BdatContent ,
234 Rcpt ,
235 RcptReply ,
236 Mail ,
237 StartTls ,
238 Secure ,
239 Vrfy ,
240 VrfyReply ,
241 Help ,
242 Auth ,
243 AuthData ,
244 Eot ,
245 Done
246 } ;
247 enum class State
248 {
249 Start ,
250 End ,
251 Idle ,
252 GotMail ,
253 GotRcpt ,
254 VrfyStart ,
255 VrfyIdle ,
256 VrfyGotMail ,
257 VrfyGotRcpt ,
258 RcptTo1 ,
259 RcptTo2 ,
260 Data ,
261 BdatData ,
262 BdatIdle ,
263 BdatDataLast ,
264 BdatChecking ,
265 MustReset ,
266 BdatProcessing ,
267 Processing ,
268 Auth ,
269 StartingTls ,
270 s_Any ,
271 s_Same
272 } ;
273 using EventData = G::string_view ;
275
276private: // overrides
277 bool sendFlush() const override ; // GSmtp::ServerSend
278
279private:
280 struct AddressCommand /// mail-from or rcpt-to
281 {
282 AddressCommand() = default ;
283 AddressCommand( const std::string & e ) : error(e) {}
284 std::string error ;
285 std::string address ;
286 std::size_t tailpos {std::string::npos} ;
287 std::size_t size {0U} ;
288 std::string auth ;
289 } ;
290 static std::unique_ptr<GAuth::SaslServer> newSaslServer( const GAuth::SaslServerSecrets & , const std::string & , const std::string & ) ;
291 static int code( EventData ) ;
292 static std::string str( EventData ) ;
293 void applyEvent( Event , EventData = {} ) ;
294 Event commandEvent( G::string_view ) const ;
295 Event dataEvent( G::string_view ) const ;
296 Event bdatEvent( G::string_view ) const ;
297 G::StringArray mechanisms() const ;
298 G::StringArray mechanisms( bool ) const ;
299 void clear() ;
300 bool messageAddContentFailed() ;
301 bool messageAddContentTooBig() ;
302 void badClientEvent() ;
303 void protocolMessageProcessed( const ProtocolMessage::ProcessedInfo & ) ;
304 bool rcptState() const ;
305 bool flush() const ;
306 bool isEndOfText( const ApplyArgsTuple & ) const ;
307 bool isEscaped( const ApplyArgsTuple & ) const ;
308 void doNoop( EventData , bool & ) ;
309 void doIgnore( EventData , bool & ) ;
310 void doNothing( EventData , bool & ) ;
311 void doHelp( EventData , bool & ) ;
312 void doExpn( EventData , bool & ) ;
313 void doQuit( EventData , bool & ) ;
314 void doEhlo( EventData , bool & ) ;
315 void doHelo( EventData , bool & ) ;
316 void doAuthInvalid( EventData , bool & ) ;
317 void doAuth( EventData , bool & ) ;
318 void doAuthData( EventData , bool & ) ;
319 void doMail( EventData , bool & ) ;
320 void doRcpt( EventData , bool & ) ;
321 void doUnknown( EventData , bool & ) ;
322 void doRset( EventData , bool & ) ;
323 void doData( EventData , bool & ) ;
324 void doDataContent( EventData , bool & ) ;
325 void doBadDataCommand( EventData , bool & ) ;
326 void doBdatOutOfSequence( EventData , bool & ) ;
327 void doBdatFirst( EventData , bool & ) ;
328 void doBdatFirstLast( EventData , bool & ) ;
329 void doBdatFirstLastZero( EventData , bool & ) ;
330 void doBdatMore( EventData , bool & ) ;
331 void doBdatMoreLast( EventData , bool & ) ;
332 void doBdatMoreLastZero( EventData , bool & ) ;
333 void doBdatImp( G::string_view , bool & , bool , bool , bool ) ;
334 void doBdatContent( EventData , bool & ) ;
335 void doBdatContentLast( EventData , bool & ) ;
336 void doBdatCheck( EventData , bool & ) ;
337 void doBdatComplete( EventData , bool & ) ;
338 void doComplete( EventData , bool & ) ;
339 void doEot( EventData , bool & ) ;
340 void doVrfy( EventData , bool & ) ;
341 void doVrfyReply( EventData , bool & ) ;
342 void doRcptToReply( EventData , bool & ) ;
343 void doNoRecipients( EventData , bool & ) ;
344 void doStartTls( EventData , bool & ) ;
345 void doSecure( EventData , bool & ) ;
346 void doSecureGreeting( EventData , bool & ) ;
347 void verifyDone( Verifier::Command , const VerifierStatus & ) ;
348 std::string useStartTls() const ;
349 void verify( Verifier::Command , const std::string & , const std::string & = {} ) ;
350
351private:
352 ServerSender * m_sender ;
353 Verifier & m_verifier ;
354 Text & m_text ;
355 ProtocolMessage & m_pm ;
356 std::unique_ptr<GAuth::SaslServer> m_sasl ;
357 Config m_config ;
358 G::Slot::Signal<> m_change_signal ;
359 const ApplyArgsTuple * m_apply_data ;
360 bool m_apply_more ;
361 Fsm m_fsm ;
362 bool m_with_starttls ;
363 GNet::Address m_peer_address ;
364 bool m_secure ;
365 std::string m_certificate ;
366 std::string m_protocol ;
367 std::string m_cipher ;
368 unsigned int m_client_error_count ;
369 std::string m_session_peer_name ;
370 bool m_session_esmtp ;
371 std::size_t m_bdat_arg ;
372 std::size_t m_bdat_sum ;
373 bool m_enabled ;
374} ;
375
376inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_with_vrfy( bool b ) noexcept { with_vrfy = b ; return *this ; }
377inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_with_chunking( bool b ) noexcept { with_chunking = b ; return *this ; }
378inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_max_size( std::size_t n ) noexcept { max_size = n ; return *this ; }
379inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_mail_requires_authentication( bool b ) noexcept { mail_requires_authentication = b ; return *this ; }
380inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_mail_requires_encryption( bool b ) noexcept { mail_requires_encryption = b ; return *this ; }
381inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_tls_starttls( bool b ) noexcept { tls_starttls = b ; return *this ; }
382inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_tls_connection( bool b ) noexcept { tls_connection = b ; return *this ; }
383inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_with_pipelining( bool b ) noexcept { with_pipelining = b ; return *this ; }
384inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_with_smtputf8( bool b ) noexcept { with_smtputf8 = b ; return *this ; }
385inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_shutdown_how_on_quit( int i ) noexcept { shutdown_how_on_quit = i ; return *this ; }
386inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_client_error_limit( unsigned int n ) noexcept { client_error_limit = n ; return *this ; }
387inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_smtputf8_strict( bool b ) noexcept { smtputf8_strict = b ; return *this ; }
388inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_sasl_server_config( const std::string & s ) { sasl_server_config = s ; return *this ; }
389inline GSmtp::ServerProtocol::Config & GSmtp::ServerProtocol::Config::set_sasl_server_challenge_hostname( const std::string & s ) { sasl_server_challenge_hostname = s ; return *this ; }
390
391#endif
An interface used by GAuth::SaslServer to obtain authentication secrets.
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:62
An interface used by the ServerProtocol class to assemble and process an incoming message.
A static mix-in class for GSmtp::ServerProtocol to do SMTP command parsing.
An interface used by GSmtp::ServerProtocol to provide response text strings.
virtual std::string greeting() const =0
Returns a system identifier for the initial greeting.
virtual std::string received(const std::string &smtp_peer_name, bool auth, bool secure, const std::string &protocol, const std::string &cipher) const =0
Returns a complete 'Received' line.
virtual ~Text()=default
Destructor.
virtual std::string hello(const std::string &smtp_peer_name) const =0
Returns a hello response.
Implements the SMTP server-side protocol.
G::Slot::Signal & changeSignal() noexcept
A signal that is emitted at the end of apply() or whenever the protocol state might have changed by s...
bool inBusyState() const
Returns true if in a state where the protocol is waiting for an asynchronous filter of address-verifi...
bool apply(const ApplyArgsTuple &)
Called on receipt of a complete line of text from the client, or possibly a line fragment iff this ob...
void setSender(ServerSender &)
Sets the ServerSender interface, overriding the constructor parameter.
void init()
Starts the protocol.
~ServerProtocol() override
Destructor.
void secure(const std::string &certificate, const std::string &protocol, const std::string &cipher)
To be called when the transport protocol successfully goes into secure mode.
bool inDataState() const
Returns true if currently in a data-transfer state meaning that the next apply() does not need to con...
ServerProtocol(ServerSender &, Verifier &, ProtocolMessage &, const GAuth::SaslServerSecrets &secrets, Text &text, const GNet::Address &peer_address, const Config &config, bool enabled)
Constructor.
A simple mix-in class for GSmtp::ServerProtocol that sends protocol responses via a GSmtp::ServerSend...
An interface used by ServerProtocol to send protocol responses.
An asynchronous interface that verifies recipient 'to' addresses.
Definition: gverifier.h:43
A class like c++17's std::string_view.
Definition: gstringview.h:51
bool enabled() noexcept
Returns true if pop code is built in.
SMTP classes.
Definition: gadminserver.h:42
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
A configuration structure for GSmtp::ServerProtocol.
A slot holder, with connect() and emit() methods.
Definition: gslot.h:184