E-MailRelay
gsmtpserver.cpp
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 gsmtpserver.cpp
19///
20
21#include "gdef.h"
22#include "gsmtpserver.h"
23#include "gresolver.h"
24#include "gdnsblock.h"
27#include "gfilterfactory.h"
28#include "gverifierfactory.h"
29#include "glocal.h"
30#include "ggettext.h"
31#include "gformat.h"
32#include "glog.h"
33#include "gassert.h"
34#include "gtest.h"
35#include <string>
36#include <functional>
37
38namespace GSmtp
39{
40 struct AnonymousText : public ServerProtocol::Text /// Provides anodyne SMTP protocol text.
41 {
42 explicit AnonymousText( const std::string & = std::string() ) ;
43 std::string greeting() const override ;
44 std::string hello( const std::string & peer_name ) const override ;
45 std::string received( const std::string & smtp_peer_name ,
46 bool auth , bool secure , const std::string & protocol ,
47 const std::string & cipher ) const override ;
48 std::string m_thishost ;
49 } ;
50}
51
52GSmtp::AnonymousText::AnonymousText( const std::string & thishost ) :
53 m_thishost(thishost)
54{
55 if( m_thishost.empty() )
56 m_thishost = "smtp" ;
57}
58
60{
61 return "ready" ;
62}
63
64std::string GSmtp::AnonymousText::hello( const std::string & ) const
65{
66 return m_thishost + " says hello" ;
67}
68
69std::string GSmtp::AnonymousText::received( const std::string & , bool , bool ,
70 const std::string & , const std::string & ) const
71{
72 return std::string() ; // no Received line
73}
74
75// ===
76
78 const GNet::ServerPeerInfo & peer_info , Server & server ,
79 const GAuth::SaslServerSecrets & server_secrets , const Server::Config & server_config ,
80 std::unique_ptr<ServerProtocol::Text> ptext ) :
81 GNet::ServerPeer(esu.bind(this),peer_info,GNet::LineBufferConfig::transparent()) ,
82 m_server(server) ,
83 m_block(*this,esu.bind(this),server_config.dnsbl_config) ,
84 m_flush_timer(*this,&ServerPeer::onFlushTimeout,esu.bind(this)) ,
85 m_check_timer(*this,&ServerPeer::onCheckTimeout,esu.bind(this)) ,
86 m_verifier(VerifierFactory::newVerifier(esu.bind(this),
87 server_config.verifier_address,server_config.verifier_timeout)) ,
88 m_pmessage(server.newProtocolMessage(esu.bind(this))) ,
89 m_ptext(ptext.release()) ,
90 m_line_buffer(GNet::LineBufferConfig::smtp()) ,
91 m_protocol(*this,*m_verifier,*m_pmessage,server_secrets,server_config.sasl_server_config,
92 *m_ptext,peer_info.m_address,server_config.protocol_config)
93{
94 G_LOG_S( "GSmtp::ServerPeer: smtp connection from " << peer_info.m_address.displayString() ) ;
95 if( server_config.dnsbl_config.empty() )
96 m_protocol.init() ;
97 else
98 m_block.start( peer_info.m_address ) ;
99
100 if( !server_config.protocol_config.tls_connection )
101 m_check_timer.startTimer( 1U ) ;
102}
103
104void GSmtp::ServerPeer::onDelete( const std::string & reason )
105{
106 G_LOG_S( "GSmtp::ServerPeer: smtp connection closed: " << reason << (reason.empty()?"":": ")
107 << peerAddress().second.displayString() ) ;
108
109 m_server.eventSignal().emit( "done" , std::string(reason) ) ;
110}
111
112void GSmtp::ServerPeer::onSendComplete()
113{
114 // never gets here -- see protocolSend()
115}
116
117void GSmtp::ServerPeer::onData( const char * data , std::size_t size )
118{
119 // discard anything received before we have even sent an initial greeting
120 if( m_block.busy() )
121 return ;
122
123 // just buffer up anything received in a half-duplex busy state
124 if( m_protocol.halfDuplexBusy(data,size) )
125 {
126 m_line_buffer.add( data , size ) ;
127 return ;
128 }
129
130 GNet::ServerPeer::onData( data , size ) ;
131}
132
133bool GSmtp::ServerPeer::onReceive( const char * data , std::size_t size , std::size_t , std::size_t , char )
134{
135 G_ASSERT( size != 0U ) ; if( size == 0U ) return true ;
136 m_line_buffer.apply( &m_protocol , &ServerProtocol::apply , data , size , &ServerProtocol::inDataState ) ;
137
138 // bad clients can send multiple commands at once, so if we have any
139 // residue in the line buffer we have to deal with it once the protocol
140 // has finished with the current one -- see protocolSend()
141 if( m_protocol.halfDuplexBusy() && !m_line_buffer.state().empty() )
142 {
143 G_WARNING( "GSmtp::ServerPeer::onReceive: smtp client protocol violation: pipelining detected" ) ;
144 m_flush_timer.startTimer( G::TimeInterval::limit() ) ; // heat-death timeout
145 }
146
147 return true ;
148}
149
150void GSmtp::ServerPeer::onFlushTimeout()
151{
152 m_line_buffer.apply( &m_protocol , &ServerProtocol::apply , nullptr ,
153 std::size_t(0U) , &ServerProtocol::inDataState ) ;
154}
155
156void GSmtp::ServerPeer::onSecure( const std::string & certificate , const std::string & protocol ,
157 const std::string & cipher )
158{
159 m_protocol.secure( certificate , protocol , cipher ) ;
160}
161
162void GSmtp::ServerPeer::protocolSend( const std::string & line , bool go_secure )
163{
164 if( !send( line , 0U ) ) // GNet::ServerPeer::send()
165 throw SendError() ; // we only send short half-duplex responses, so treat flow control as fatal
166
167 if( go_secure )
168 secureAccept() ;
169
170 if( m_flush_timer.active() )
171 {
172 G_DEBUG( "GSmtp::ServerPeer::protocolSend: pipeline released (" << m_line_buffer.state().size() << ")" ) ;
173 m_flush_timer.startTimer( 0U ) ;
174 }
175}
176
177void GSmtp::ServerPeer::protocolShutdown()
178{
179 m_flush_timer.cancelTimer() ;
180 socket().shutdown() ; // fwiw
181}
182
183void GSmtp::ServerPeer::onDnsBlockResult( const GNet::DnsBlockResult & result )
184{
185 result.log() ;
186 result.warn() ;
187 if( result.allow() )
188 m_protocol.init() ;
189 else
190 throw GNet::Done() ;
191}
192
193void GSmtp::ServerPeer::onCheckTimeout()
194{
195 // do a better-than-nothing check for an unexpected TLS ClientHello -- false
196 // positives are possible but extremely unlikely
197 std::string head = m_line_buffer.state().head() ;
198 if( head.size() > 6U && head.at(0U) == '\x16' && head.at(1U) == '\x03' &&
199 ( head.at(2U) == '\x03' || head.at(2U) == '\x02' || head.at(2U) == '\01' ) )
200 G_WARNING( "GSmtp::ServerPeer::doCheck: received unexpected tls handshake packet from remote client: "
201 "try enabling implicit tls (smtps)" ) ;
202}
203
204// ===
205
207 const GAuth::SaslClientSecrets & client_secrets , const GAuth::SaslServerSecrets & server_secrets ,
208 const Config & server_config , const std::string & forward_to ,
209 const GSmtp::Client::Config & client_config ) :
210 GNet::MultiServer(es,server_config.interfaces,server_config.port,"smtp",server_config.server_peer_config,server_config.server_config) ,
211 m_store(store) ,
212 m_ff(ff) ,
213 m_server_config(server_config) ,
214 m_client_config(client_config) ,
215 m_server_secrets(server_secrets) ,
216 m_forward_to(forward_to) ,
217 m_client_secrets(client_secrets)
218{
219}
220
222{
223 serverCleanup() ; // base class early cleanup
224}
225
227{
228 return m_event_signal ;
229}
230
232{
233 serverReport() ; // base class
234}
235
236std::unique_ptr<GNet::ServerPeer> GSmtp::Server::newPeer( GNet::ExceptionSinkUnbound esu ,
238{
239 using G::format ;
240 using G::gettext ;
241 std::unique_ptr<ServerPeer> ptr ;
242 try
243 {
244 std::string reason ;
245 if( ! m_server_config.allow_remote && ! GNet::Local::isLocal(peer_info.m_address,reason) )
246 {
247 G_WARNING( "GSmtp::Server: "
248 << format(gettext("configured to reject non-local smtp connection: %1%")) % reason ) ;
249 }
250 else
251 {
252 ptr = std::make_unique<ServerPeer>( esu , peer_info , *this ,
253 m_server_secrets , m_server_config ,
254 newProtocolText(m_server_config.anonymous,peer_info.m_address) ) ;
255 }
256 }
257 catch( std::exception & e ) // newPeer()
258 {
259 G_WARNING( "GSmtp::Server: new connection error: " << e.what() ) ;
260 }
261 return std::unique_ptr<GNet::ServerPeer>( ptr.release() ) ; // up-cast
262}
263
264std::unique_ptr<GSmtp::ServerProtocol::Text> GSmtp::Server::newProtocolText( bool anonymous ,
265 const GNet::Address & peer_address ) const
266{
267 if( anonymous )
268 return std::make_unique<AnonymousText>() ; // up-cast
269 else
270 return std::make_unique<ServerProtocolText>( m_server_config.ident ,
271 GNet::Local::canonicalName() , peer_address ) ; // up-cast
272}
273
274std::unique_ptr<GSmtp::Filter> GSmtp::Server::newFilter( GNet::ExceptionSink es ) const
275{
276 return m_ff.newFilter( es , true , m_server_config.filter_address , m_server_config.filter_timeout ) ;
277}
278
279std::unique_ptr<GSmtp::ProtocolMessage> GSmtp::Server::newProtocolMessageStore( std::unique_ptr<Filter> filter )
280{
281 return std::make_unique<ProtocolMessageStore>( m_store , std::move(filter) ) ; // up-cast
282}
283
284std::unique_ptr<GSmtp::ProtocolMessage> GSmtp::Server::newProtocolMessageForward( GNet::ExceptionSink es ,
285 std::unique_ptr<ProtocolMessage> pm )
286{
287 // wrap the given 'store' object in a 'forward' one
288 return std::make_unique<ProtocolMessageForward>( es , m_store , m_ff , std::move(pm) , m_client_config ,
289 m_client_secrets , m_forward_to ) ; // up-cast
290}
291
292std::unique_ptr<GSmtp::ProtocolMessage> GSmtp::Server::newProtocolMessage( GNet::ExceptionSink es )
293{
294 const bool do_forward = ! m_forward_to.empty() ;
295 return do_forward ?
296 newProtocolMessageForward( es , newProtocolMessageStore(newFilter(es)) ) :
297 newProtocolMessageStore( newFilter(es) ) ;
298}
299
300// ===
301
302GSmtp::Server::Config::Config()
303= default;
304
305GSmtp::Server::Config::Config( bool allow_remote_ , const G::StringArray & interfaces_ ,
306 unsigned int port_ ,
307 const std::string & ident_ , bool anonymous_ ,
308 const std::string & filter_address_ ,
309 unsigned int filter_timeout_ ,
310 const std::string & verifier_address_ ,
311 unsigned int verifier_timeout_ ,
312 GNet::ServerPeerConfig server_peer_config_ ,
313 ServerProtocol::Config protocol_config_ ,
314 const std::string & sasl_server_config_ ,
315 const std::string & dnsbl_config_ ) :
316 allow_remote(allow_remote_) ,
317 interfaces(interfaces_) ,
318 port(port_) ,
319 ident(ident_) ,
320 anonymous(anonymous_) ,
321 filter_address(filter_address_) ,
322 filter_timeout(filter_timeout_) ,
323 verifier_address(verifier_address_) ,
324 verifier_timeout(verifier_timeout_) ,
325 server_peer_config(server_peer_config_) ,
326 protocol_config(protocol_config_) ,
327 sasl_server_config(sasl_server_config_) ,
328 dnsbl_config(dnsbl_config_)
329{
330}
331
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret.
An interface used by GAuth::SaslServer to obtain authentication secrets.
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:53
std::string displayString(bool with_scope_id=false) const
Returns a string which represents the transport address.
Definition: gaddress.cpp:375
A result structure for GNet::DnsBlock, as delivered by the DnsBlockCallback interface.
Definition: gdnsblock.h:78
void warn() const
Emits warnings.
Definition: gdnsblock.cpp:283
bool allow() const
Returns true if the type is Inactive, Local, TimeoutAllow or Allow.
Definition: gdnsblock.cpp:299
void log() const
Logs the results.
Definition: gdnsblock.cpp:259
void start(const Address &)
Starts an asychronous check on the given address.
Definition: gdnsblock.cpp:146
An exception class that is detected by GNet::EventHandlerList and results in onException() being call...
Definition: gnetdone.h:40
A potential ExceptionSink that is realised by bind()ing an exception source pointer.
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
static bool isLocal(const Address &, std::string &reason)
Returns true if the given address appears to be 'local', or a helpful error message if not.
Definition: glocal.cpp:64
static std::string canonicalName()
Returns the canonical network name assiciated with hostname().
Definition: glocal.cpp:53
A structure that GNet::Server uses to configure its ServerPeer objects.
Definition: gserverpeer.h:51
A structure used in GNet::Server::newPeer().
Definition: gserver.h:142
void onData(const char *, std::size_t) override
Override from GNet::SocketProtocolSink.
A factory interface for GSmtp::Filter message processors.
A class which allows SMTP messages to be stored and retrieved.
Definition: gmessagestore.h:73
Handles a connection from a remote SMTP client.
Definition: gsmtpserver.h:150
ServerPeer(GNet::ExceptionSinkUnbound, const GNet::ServerPeerInfo &peer_info, Server &server, const GAuth::SaslServerSecrets &server_secrets, const Server::Config &server_config, std::unique_ptr< ServerProtocol::Text > ptext)
Constructor.
Definition: gsmtpserver.cpp:77
An interface used by ServerProtocol to provide response text strings.
bool apply(const char *line_data, std::size_t line_size, std::size_t eolsize, std::size_t linesize, char c0)
Called on receipt of a line of text from the remote client.
void init()
Starts the protocol.
bool inDataState() const
Returns true if currently in the data-transfer state.
An SMTP server class.
Definition: gsmtpserver.h:50
G::Slot::Signal< const std::string &, const std::string & > & eventSignal()
Returns a signal that indicates that something has happened.
~Server() override
Destructor.
Server(GNet::ExceptionSink es, MessageStore &store, FilterFactory &, const GAuth::SaslClientSecrets &client_secrets, const GAuth::SaslServerSecrets &server_secrets, const Config &server_config, const std::string &forward_to, const GSmtp::Client::Config &client_config)
Constructor.
std::unique_ptr< ProtocolMessage > newProtocolMessage(GNet::ExceptionSink)
Called by GSmtp::ServerPeer to construct a ProtocolMessage.
void report() const
Generates helpful diagnostics after construction.
A factory for addresss verifiers.
static TimeInterval limit()
Factory function for the maximum valid interval.
Definition: gdatetime.cpp:527
A simple version of boost::format for formatting strings in an i18n-friendly way.
Definition: gformat.h:46
Network classes.
Definition: gdef.h:1115
SMTP and message-store classes.
Definition: gadminserver.h:39
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
const char * gettext(const char *)
Returns the message translation in the current locale's codeset, eg.
Definition: ggettext.h:69
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:52
Provides anodyne SMTP protocol text.
Definition: gsmtpserver.cpp:41
std::string greeting() const override
Returns a system identifier for the initial greeting.
Definition: gsmtpserver.cpp:59
std::string received(const std::string &smtp_peer_name, bool auth, bool secure, const std::string &protocol, const std::string &cipher) const override
Returns a complete 'Received' line.
Definition: gsmtpserver.cpp:69
std::string hello(const std::string &peer_name) const override
Returns a hello response.
Definition: gsmtpserver.cpp:64
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:57
A structure containing configuration parameters for ServerProtocol.
A structure containing GSmtp::Server configuration parameters.
Definition: gsmtpserver.h:55