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