E-MailRelay
gsmtpserver.cpp
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 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(esbind(esu,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),esbind(esu,this),server_config.dnsbl_config) ,
44 m_check_timer(*this,&ServerPeer::onCheckTimeout,esbind(esu,this)) ,
45 m_verifier(vf.newVerifier(esbind(esu,this),server_config.verifier_config,server_config.verifier_spec)) ,
46 m_pmessage(server.newProtocolMessage(esbind(esu,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(esbind(esu,this),m_protocol,server_config.buffer_config)
52{
53 G_LOG_S( "GSmtp::ServerPeer: smtp connection from " << peerAddress().displayString() ) ;
54
55 if( !server_config.protocol_config.tls_connection )
56 m_check_timer.startTimer( 1U ) ;
57
58 if( server_config.dnsbl_config.empty() )
59 m_protocol.init() ;
60 else
61 m_block.start( peerAddress() ) ;
62
63 m_input_buffer.flowSignal().connect( G::Slot::slot(*this,&ServerPeer::onFlow) ) ;
64}
65
67{
68 m_input_buffer.flowSignal().disconnect() ;
69}
70
71void GSmtp::ServerPeer::onDelete( const std::string & reason )
72{
73 G_LOG_S( "GSmtp::ServerPeer: smtp connection closed: " << reason << (reason.empty()?"":": ")
74 << peerAddress().displayString() ) ;
75
76 m_server.eventSignal().emit( "done" , std::string(reason) ) ;
77}
78
79void GSmtp::ServerPeer::onData( const char * data , std::size_t size )
80{
81 G_ASSERT( data != nullptr && size != 0U ) ;
82
83 // this override intercepts incoming data before it is applied to the
84 // base class's line buffer so that we can discard anything received
85 // before we have even sent an initial greeting
86 if( m_block.busy() )
87 return ;
88
89 // the base class's line buffer is configured as transparent so
90 // this is effectively a direct call to onReceive() -- we go via
91 // the base class only in order to kick its idle-timeout timer
92 GNet::ServerPeer::onData( data , size ) ;
93}
94
95bool GSmtp::ServerPeer::onReceive( const char * data , std::size_t size , std::size_t , std::size_t , char )
96{
97 G_ASSERT( data != nullptr && size != 0U ) ;
98 m_input_buffer.apply( data , size ) ;
99 return true ;
100}
101
102void GSmtp::ServerPeer::onFlow( bool on )
103{
104 if( on )
105 addReadHandler() ;
106 else
107 dropReadHandler() ;
108}
109
110void GSmtp::ServerPeer::protocolSecure()
111{
112 secureAccept() ;
113}
114
115void GSmtp::ServerPeer::onSecure( const std::string & certificate , const std::string & protocol ,
116 const std::string & cipher )
117{
118 m_protocol.secure( certificate , protocol , cipher ) ;
119}
120
121void GSmtp::ServerPeer::protocolSend( const std::string & line , bool )
122{
123 G_ASSERT( !line.empty() ) ;
124 if( m_output_blocked )
125 {
126 m_output_buffer.append( line ) ;
127 }
128 else if( !send( line ) )
129 {
130 m_output_blocked = true ;
131 m_output_buffer.clear() ;
132 }
133}
134
135void GSmtp::ServerPeer::onSendComplete()
136{
137 G_ASSERT( m_output_blocked ) ;
138 if( send( m_output_buffer ) ) // GNet::ServerPeer::send()
139 m_output_blocked = false ;
140 m_output_buffer.clear() ;
141}
142
143void GSmtp::ServerPeer::protocolShutdown( int how )
144{
145 if( how >= 0 )
146 socket().shutdown( how ) ;
147}
148
149void GSmtp::ServerPeer::protocolExpect( std::size_t n )
150{
151 m_input_buffer.expect( n ) ;
152}
153
154void GSmtp::ServerPeer::onDnsBlockResult( bool allow )
155{
156 if( allow )
157 m_protocol.init() ;
158 else
159 throw GNet::Done() ;
160}
161
162void GSmtp::ServerPeer::onCheckTimeout()
163{
164 // do a better-than-nothing check for an unexpected TLS ClientHello -- false
165 // positives are possible but extremely unlikely
166 std::string head = m_input_buffer.head() ;
167 if( head.size() > 6U && head.at(0U) == '\x16' && head.at(1U) == '\x03' &&
168 ( head.at(2U) == '\x03' || head.at(2U) == '\x02' || head.at(2U) == '\01' ) )
169 G_WARNING( "GSmtp::ServerPeer::doCheck: received unexpected tls handshake packet from remote client: "
170 "try enabling implicit tls (smtps)" ) ;
171}
172
173// ===
174
176 VerifierFactoryBase & vf , const GAuth::SaslClientSecrets & client_secrets ,
177 const GAuth::SaslServerSecrets & server_secrets , const Config & server_config ,
178 const std::string & forward_to , int forward_to_family ,
179 const GSmtp::Client::Config & client_config ) :
180 GNet::MultiServer(es,server_config.interfaces,server_config.port,"smtp",
181 server_config.net_server_peer_config,
182 server_config.net_server_config) ,
183 m_store(store) ,
184 m_ff(ff) ,
185 m_vf(vf) ,
186 m_server_config(server_config) ,
187 m_client_config(client_config) ,
188 m_server_secrets(server_secrets) ,
189 m_forward_to(forward_to) ,
190 m_forward_to_family(forward_to_family) ,
191 m_client_secrets(client_secrets) ,
192 m_dnsbl_suspend_time(G::TimerTime::zero())
193{
194}
195
197{
198 serverCleanup() ; // base class early cleanup
199}
200
201#ifndef G_LIB_SMALL
203{
204 return m_server_config ;
205}
206#endif
207
209{
210 return m_event_signal ;
211}
212
213void GSmtp::Server::report( const std::string & group ) const
214{
215 serverReport( group ) ; // base class
216}
217
218std::unique_ptr<GNet::ServerPeer> GSmtp::Server::newPeer( GNet::EventStateUnbound esu ,
220{
221 using G::format ;
222 using G::txt ;
223 std::unique_ptr<ServerPeer> ptr ;
224 try
225 {
226 std::string reason ;
227 if( ! m_server_config.allow_remote && !peer_info.m_address.isLocal(reason) )
228 {
229 G_WARNING( "GSmtp::Server: "
230 << format(txt("configured to reject non-local smtp connection: %1%")) % reason ) ;
231 }
232 else
233 {
234 GNet::Address peer_address = peer_info.m_address ;
235 ptr = std::make_unique<ServerPeer>( esu , std::move(peer_info) , *this ,
236 m_enabled , m_vf , m_server_secrets , serverConfig() ,
237 newProtocolText(m_server_config.anonymous_smtp,m_server_config.anonymous_content,peer_address,m_server_config.domain) ) ;
238 }
239 }
240 catch( std::exception & e ) // newPeer()
241 {
242 G_WARNING( "GSmtp::Server: new connection error: " << e.what() ) ;
243 }
244 return ptr ;
245}
246
247GSmtp::Server::Config GSmtp::Server::serverConfig() const
248{
249 if( !m_dnsbl_suspend_time.isZero() && G::TimerTime::now() < m_dnsbl_suspend_time )
250 return Config(m_server_config).set_dnsbl_config({}) ;
251 return m_server_config ;
252}
253
254void GSmtp::Server::nodnsbl( unsigned int s )
255{
256 G_LOG( "GSmtp::Server::nodnsbl: dnsbl " << (s?"disabled":"enabled") << (s?(" for "+G::Str::fromUInt(s).append(1U,'s')):"") ) ;
257 m_dnsbl_suspend_time = G::TimerTime::now() + G::TimeInterval(s) ;
258}
259
261{
262 m_enabled = b ;
263}
264
265std::unique_ptr<GSmtp::ServerProtocol::Text> GSmtp::Server::newProtocolText( bool anonymous_smtp ,
266 bool anonymous_content , const GNet::Address & peer_address , const std::string & domain ) const
267{
268 const bool with_received_line = !anonymous_content ;
269 return std::make_unique<ServerText>( m_server_config.ident , anonymous_smtp , with_received_line ,
270 domain , peer_address ) ;
271}
272
273std::unique_ptr<GSmtp::Filter> GSmtp::Server::newFilter( GNet::EventState es ) const
274{
275 return m_ff.newFilter( es , Filter::Type::server , m_server_config.filter_config ,
276 m_server_config.filter_spec ) ;
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) ) ;
282}
283
284std::unique_ptr<GSmtp::ProtocolMessage> GSmtp::Server::newProtocolMessageForward( GNet::EventState 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 , m_forward_to_family ) ; // up-cast
290}
291
292std::unique_ptr<GSmtp::ProtocolMessage> GSmtp::Server::newProtocolMessage( GNet::EventState 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
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:63
void start(const Address &)
Starts an asychronous check on the given address.
An exception class that is caught separately by GNet::EventEmitter and GNet::TimerList so that onExce...
Definition: gnetdone.h:40
The EventStateUnbound class is used as a device to force factory methods to plumb-in an ExceptionSour...
Definition: geventstate.h:231
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
A move-only structure used in GNet::Server::newPeer() and containing the new socket.
Definition: gserver.h:142
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() override
Destructor.
Definition: gsmtpserver.cpp:66
ServerPeer(GNet::EventStateUnbound, 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
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.
Config & config()
Exposes the configuration sub-object.
Server(GNet::EventState 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.
std::unique_ptr< ProtocolMessage > newProtocolMessage(GNet::EventState)
Called by GSmtp::ServerPeer to construct a ProtocolMessage.
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.
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:612
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:305
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:1243
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:36
const char * txt(const char *p) noexcept
A briefer alternative to G::gettext().
Definition: ggettext.h:74
STL namespace.
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:55
A configuration structure for GNet::ServerPeer.
Definition: gserverpeer.h:64
A structure containing GSmtp::Client configuration parameters.
Definition: gsmtpclient.h:67
A configuration structure for GSmtp::Server.
Definition: gsmtpserver.h:60