E-MailRelay
gsmtpserver.cpp
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 gsmtpserver.cpp
19///
20
21#include "gdef.h"
22#include "gsmtpserver.h"
23#include "gsmtpservertext.h"
24#include "gdnsblock.h"
27#include "gfilterfactorybase.h"
29#include "ggettext.h"
30#include "gformat.h"
31#include "glog.h"
32#include "gassert.h"
33#include <string>
34#include <functional>
35
37 GNet::ServerPeerInfo && peer_info , Server & server , bool enabled , VerifierFactoryBase & vf ,
38 const GAuth::SaslServerSecrets & server_secrets , const Server::Config & server_config ,
39 std::unique_ptr<ServerProtocol::Text> ptext ) :
40 GNet::ServerPeer(esu.bind(this),std::move(peer_info),GNet::LineBuffer::Config::transparent()) ,
41 m_server(server) ,
42 m_server_config(server_config) ,
43 m_block(std::bind(&ServerPeer::onDnsBlockResult,this,std::placeholders::_1),esu.bind(this),server_config.dnsbl_config) ,
44 m_check_timer(*this,&ServerPeer::onCheckTimeout,esu.bind(this)) ,
45 m_verifier(vf.newVerifier(esu.bind(this),server_config.verifier_config,server_config.verifier_spec)) ,
46 m_pmessage(server.newProtocolMessage(esu.bind(this))) ,
47 m_ptext(ptext.release()) ,
48 m_protocol(*this,*m_verifier,*m_pmessage,server_secrets,
49 *m_ptext,peerAddress(),
50 server_config.protocol_config,enabled) ,
51 m_input_buffer(esu.bind(this),m_protocol,server_config.buffer_config) ,
52 m_output_blocked(false)
53{
54 G_LOG_S( "GSmtp::ServerPeer: smtp connection from " << peerAddress().displayString() ) ;
55
56 if( !server_config.protocol_config.tls_connection )
57 m_check_timer.startTimer( 1U ) ;
58
59 if( server_config.dnsbl_config.empty() )
60 m_protocol.init() ;
61 else
62 m_block.start( peerAddress() ) ;
63
64 m_input_buffer.flowSignal().connect( G::Slot::slot(*this,&ServerPeer::onFlow) ) ;
65}
66
68{
69 m_input_buffer.flowSignal().disconnect() ;
70}
71
72void GSmtp::ServerPeer::onDelete( const std::string & reason )
73{
74 G_LOG_S( "GSmtp::ServerPeer: smtp connection closed: " << reason << (reason.empty()?"":": ")
75 << peerAddress().displayString() ) ;
76
77 m_server.eventSignal().emit( "done" , std::string(reason) ) ;
78}
79
80void GSmtp::ServerPeer::onData( const char * data , std::size_t size )
81{
82 G_ASSERT( data != nullptr && size != 0U ) ;
83
84 // this override intercepts incoming data before it is applied to the
85 // base class's line buffer so that we can discard anything received
86 // before we have even sent an initial greeting
87 if( m_block.busy() )
88 return ;
89
90 // the base class's line buffer is configured as transparent so
91 // this is effectively a direct call to onReceive() -- we go via
92 // the base class only in order to kick its idle-timeout timer
93 GNet::ServerPeer::onData( data , size ) ;
94}
95
96bool GSmtp::ServerPeer::onReceive( const char * data , std::size_t size , std::size_t , std::size_t , char )
97{
98 G_ASSERT( data != nullptr && size != 0U ) ;
99 m_input_buffer.apply( data , size ) ;
100 return true ;
101}
102
103void GSmtp::ServerPeer::onFlow( bool on )
104{
105 if( on )
106 addReadHandler() ;
107 else
108 dropReadHandler() ;
109}
110
111void GSmtp::ServerPeer::protocolSecure()
112{
113 secureAccept() ;
114}
115
116void GSmtp::ServerPeer::onSecure( const std::string & certificate , const std::string & protocol ,
117 const std::string & cipher )
118{
119 m_protocol.secure( certificate , protocol , cipher ) ;
120}
121
122void GSmtp::ServerPeer::protocolSend( const std::string & line , bool )
123{
124 G_ASSERT( !line.empty() ) ;
125 if( m_output_blocked )
126 {
127 m_output_buffer.append( line ) ;
128 }
129 else if( !send( line ) )
130 {
131 m_output_blocked = true ;
132 m_output_buffer.clear() ;
133 }
134}
135
136void GSmtp::ServerPeer::onSendComplete()
137{
138 G_ASSERT( m_output_blocked ) ;
139 if( send( m_output_buffer ) ) // GNet::ServerPeer::send()
140 m_output_blocked = false ;
141 m_output_buffer.clear() ;
142}
143
144void GSmtp::ServerPeer::protocolShutdown( int how )
145{
146 if( how >= 0 )
147 socket().shutdown( how ) ;
148}
149
150void GSmtp::ServerPeer::protocolExpect( std::size_t n )
151{
152 m_input_buffer.expect( n ) ;
153}
154
155void GSmtp::ServerPeer::onDnsBlockResult( bool allow )
156{
157 if( allow )
158 m_protocol.init() ;
159 else
160 throw GNet::Done() ;
161}
162
163void GSmtp::ServerPeer::onCheckTimeout()
164{
165 // do a better-than-nothing check for an unexpected TLS ClientHello -- false
166 // positives are possible but extremely unlikely
167 std::string head = m_input_buffer.head() ;
168 if( head.size() > 6U && head.at(0U) == '\x16' && head.at(1U) == '\x03' &&
169 ( head.at(2U) == '\x03' || head.at(2U) == '\x02' || head.at(2U) == '\01' ) )
170 G_WARNING( "GSmtp::ServerPeer::doCheck: received unexpected tls handshake packet from remote client: "
171 "try enabling implicit tls (smtps)" ) ;
172}
173
174// ===
175
177 VerifierFactoryBase & vf , const GAuth::SaslClientSecrets & client_secrets ,
178 const GAuth::SaslServerSecrets & server_secrets , const Config & server_config ,
179 const std::string & forward_to , int forward_to_family ,
180 const GSmtp::Client::Config & client_config ) :
181 GNet::MultiServer(es,server_config.interfaces,server_config.port,"smtp",
182 server_config.net_server_peer_config,
183 server_config.net_server_config) ,
184 m_store(store) ,
185 m_ff(ff) ,
186 m_vf(vf) ,
187 m_server_config(server_config) ,
188 m_client_config(client_config) ,
189 m_server_secrets(server_secrets) ,
190 m_forward_to(forward_to) ,
191 m_forward_to_family(forward_to_family) ,
192 m_client_secrets(client_secrets) ,
193 m_dnsbl_suspend_time(G::TimerTime::zero()) ,
194 m_enabled(true)
195{
196}
197
199{
200 serverCleanup() ; // base class early cleanup
201}
202
203#ifndef G_LIB_SMALL
205{
206 return m_server_config ;
207}
208#endif
209
211{
212 return m_event_signal ;
213}
214
215void GSmtp::Server::report( const std::string & group ) const
216{
217 serverReport( group ) ; // base class
218}
219
220std::unique_ptr<GNet::ServerPeer> GSmtp::Server::newPeer( GNet::ExceptionSinkUnbound esu ,
222{
223 using G::format ;
224 using G::txt ;
225 std::unique_ptr<ServerPeer> ptr ;
226 try
227 {
228 std::string reason ;
229 if( ! m_server_config.allow_remote && !peer_info.m_address.isLocal(reason) )
230 {
231 G_WARNING( "GSmtp::Server: "
232 << format(txt("configured to reject non-local smtp connection: %1%")) % reason ) ;
233 }
234 else
235 {
236 GNet::Address peer_address = peer_info.m_address ;
237 ptr = std::make_unique<ServerPeer>( esu , std::move(peer_info) , *this ,
238 m_enabled , m_vf , m_server_secrets , serverConfig() ,
239 newProtocolText(m_server_config.anonymous_smtp,m_server_config.anonymous_content,peer_address,m_server_config.domain) ) ;
240 }
241 }
242 catch( std::exception & e ) // newPeer()
243 {
244 G_WARNING( "GSmtp::Server: new connection error: " << e.what() ) ;
245 }
246 return std::unique_ptr<GNet::ServerPeer>( ptr.release() ) ; // up-cast
247}
248
249GSmtp::Server::Config GSmtp::Server::serverConfig() const
250{
251 if( !m_dnsbl_suspend_time.isZero() && G::TimerTime::now() < m_dnsbl_suspend_time )
252 return Config(m_server_config).set_dnsbl_config({}) ;
253 return m_server_config ;
254}
255
256void GSmtp::Server::nodnsbl( unsigned int s )
257{
258 G_LOG( "GSmtp::Server::nodnsbl: dnsbl " << (s?"disabled":"enabled") << (s?(" for "+G::Str::fromUInt(s).append(1U,'s')):"") ) ;
259 m_dnsbl_suspend_time = G::TimerTime::now() + G::TimeInterval(s) ;
260}
261
263{
264 m_enabled = b ;
265}
266
267std::unique_ptr<GSmtp::ServerProtocol::Text> GSmtp::Server::newProtocolText( bool anonymous_smtp ,
268 bool anonymous_content , const GNet::Address & peer_address , const std::string & domain ) const
269{
270 const bool with_received_line = !anonymous_content ;
271 return std::make_unique<ServerText>( m_server_config.ident , anonymous_smtp , with_received_line ,
272 domain , peer_address ) ;
273}
274
275std::unique_ptr<GSmtp::Filter> GSmtp::Server::newFilter( GNet::ExceptionSink es ) const
276{
277 return m_ff.newFilter( es , Filter::Type::server , m_server_config.filter_config ,
278 m_server_config.filter_spec ) ;
279}
280
281std::unique_ptr<GSmtp::ProtocolMessage> GSmtp::Server::newProtocolMessageStore( std::unique_ptr<Filter> filter )
282{
283 return std::make_unique<ProtocolMessageStore>( m_store , std::move(filter) ) ;
284}
285
286std::unique_ptr<GSmtp::ProtocolMessage> GSmtp::Server::newProtocolMessageForward( GNet::ExceptionSink es ,
287 std::unique_ptr<ProtocolMessage> pm )
288{
289 // wrap the given 'store' object in a 'forward' one
290 return std::make_unique<ProtocolMessageForward>( es , m_store , m_ff , std::move(pm) , m_client_config ,
291 m_client_secrets , m_forward_to , m_forward_to_family ) ; // up-cast
292}
293
294std::unique_ptr<GSmtp::ProtocolMessage> GSmtp::Server::newProtocolMessage( GNet::ExceptionSink es )
295{
296 const bool do_forward = ! m_forward_to.empty() ;
297 return do_forward ?
298 newProtocolMessageForward( es , newProtocolMessageStore(newFilter(es)) ) :
299 newProtocolMessageStore( newFilter(es) ) ;
300}
301
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:62
void start(const Address &)
Starts an asychronous check on the given address.
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.
A move-only structure used in GNet::Server::newPeer() and containing the new socket.
Definition: gserver.h:140
Address peerAddress() const override
Returns the peer address.
void onData(const char *, std::size_t) override
Override from GNet::SocketProtocolSink.
A factory interface for making GSmtp::Filter message processors.
G::Slot::Signal< bool > & flowSignal() noexcept
Returns a signal that should be connected to a function that controls network flow control,...
Handles a connection from a remote SMTP client.
Definition: gsmtpserver.h:176
ServerPeer(GNet::ExceptionSinkUnbound, GNet::ServerPeerInfo &&peer_info, Server &server, bool enabled, VerifierFactoryBase &vf, const GAuth::SaslServerSecrets &server_secrets, const Server::Config &server_config, std::unique_ptr< ServerProtocol::Text > ptext)
Constructor.
Definition: gsmtpserver.cpp:36
~ServerPeer() override
Destructor.
Definition: gsmtpserver.cpp:67
void init()
Starts the protocol.
An SMTP server class.
Definition: gsmtpserver.h:55
void enable(bool)
Disables or re-enables new SMTP sessions.
void report(const std::string &group={}) const
Generates helpful diagnostics after construction.
Server(GNet::ExceptionSink es, GStore::MessageStore &, FilterFactoryBase &, VerifierFactoryBase &, const GAuth::SaslClientSecrets &, const GAuth::SaslServerSecrets &, const Config &server_config, const std::string &forward_to, int forward_to_family, const GSmtp::Client::Config &client_config)
Constructor.
Config & config()
Exposes the configuration sub-object.
void nodnsbl(unsigned int seconds)
Clears the DNSBL configuration string for a period of time.
~Server() override
Destructor.
G::Slot::Signal< const std::string &, const std::string & > & eventSignal() noexcept
Returns a signal that indicates that something has happened.
std::unique_ptr< ProtocolMessage > newProtocolMessage(GNet::ExceptionSink)
Called by GSmtp::ServerPeer to construct a ProtocolMessage.
A factory interface for addresss verifiers.
A class which allows SMTP messages to be stored and retrieved.
Definition: gmessagestore.h:73
static std::string fromUInt(unsigned int ui)
Converts unsigned int 'ui' to a string.
Definition: gstr.h:616
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:299
static TimerTime now()
Factory function for the current steady-clock time.
Definition: gdatetime.cpp:479
A simple version of boost::format for formatting strings in an i18n-friendly way.
Definition: gformat.h:46
Network classes.
Definition: gdef.h:1144
bool enabled() noexcept
Returns true if pop code is built in.
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:240
Low-level classes.
Definition: garg.h:30
const char * txt(const char *p)
A briefer alternative to G::gettext().
Definition: ggettext.h:74
STL namespace.
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:56
A configuration structure for GNet::ServerPeer.
Definition: gserverpeer.h:63
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:66
A configuration structure for GSmtp::Server.
Definition: gsmtpserver.h:60