E-MailRelay
gmxfilter.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 gmxfilter.cpp
19///
20
21#include "gdef.h"
22#include "gmxfilter.h"
23#include "gstoredfile.h"
24#include "gprocess.h"
25#include "gaddress.h"
26#include "gnameservers.h"
27#include "gexception.h"
28#include "gscope.h"
29#include "gstringtoken.h"
30#include "glog.h"
31
33 Filter::Type filter_type , const Filter::Config & filter_config , const std::string & spec ) :
34 m_es(es) ,
35 m_store(store) ,
36 m_filter_type(filter_type) ,
37 m_filter_config(filter_config) ,
38 m_spec(spec) ,
39 m_id("mx:") ,
40 m_result(Result::fail) ,
41 m_special(false) ,
42 m_timer(*this,&MxFilter::onTimeout,es)
43{
44 if( !MxLookup::enabled() )
45 throw G::Exception( "mx: not enabled at build time" ) ;
46}
47
49{
50 if( m_lookup )
51 m_lookup->doneSignal().disconnect() ;
52}
53
54void GFilters::MxFilter::start( const GStore::MessageId & message_id )
55{
56 G::Path envelope_path = m_store.envelopePath( message_id , storestate() ) ;
57 GStore::Envelope envelope = GStore::FileStore::readEnvelope( envelope_path ) ;
58 unsigned int port = parseForwardToPort( envelope.forward_to ) ;
59 std::string domain = parseForwardToDomain( envelope.forward_to ) ;
60 if( domain.empty() )
61 {
62 m_timer.startTimer( 0U ) ;
63 m_result = Result::ok ;
64 }
65 else
66 {
67 G_LOG( "GFilters::MxFilter::start: " << prefix() << " looking up [" << domain << "]" ) ;
68
69 if( m_lookup ) m_lookup->doneSignal().disconnect() ;
70 m_lookup = std::make_unique<MxLookup>( m_es , mxconfig(m_spec) , mxnameservers(m_spec) ) ;
71 m_lookup->doneSignal().connect( G::Slot::slot(*this,&MxFilter::lookupDone) ) ;
72
73 m_lookup->start( message_id , domain , port ) ;
74 if( m_filter_config.timeout )
75 m_timer.startTimer( m_filter_config.timeout ) ;
76 else
77 m_timer.cancelTimer() ;
78 }
79}
80
81std::string GFilters::MxFilter::id() const
82{
83 return m_id ;
84}
85
86bool GFilters::MxFilter::quiet() const
87{
88 return false ;
89}
90
91bool GFilters::MxFilter::special() const
92{
93 return m_special ;
94}
95
96GSmtp::Filter::Result GFilters::MxFilter::result() const
97{
98 return m_result ;
99}
100
101std::string GFilters::MxFilter::response() const
102{
103 return std::string( m_result == Result::fail ? "failed" : "" ) ;
104}
105
106int GFilters::MxFilter::responseCode() const
107{
108 return 0 ;
109}
110
111std::string GFilters::MxFilter::reason() const
112{
113 return response() ;
114}
115
116G::Slot::Signal<int> & GFilters::MxFilter::doneSignal() noexcept
117{
118 return m_done_signal ;
119}
120
121void GFilters::MxFilter::cancel()
122{
123 if( m_lookup )
124 m_lookup->cancel() ;
125}
126
127void GFilters::MxFilter::onTimeout()
128{
129 G_DEBUG( "GFilters::MxFilter::onTimeout: response=[" << response() << "] special=" << m_special ) ;
130 m_done_signal.emit( static_cast<int>(m_result) ) ;
131}
132
133void GFilters::MxFilter::lookupDone( GStore::MessageId message_id , std::string address , std::string error )
134{
135 G_ASSERT( address.empty() == !error.empty() ) ;
136
137 // allow a special IP address to mean no forward-to-address
138 if( G::Str::headMatch(address,"0.0.0.0:") && GNet::Address::validString(address) )
139 address.clear() ;
140
141 G_LOG( "GFilters::MxFilter::start: " << prefix() << ": [" << message_id.str() << "]: "
142 << "setting forward-to-address [" << address << "]"
143 << (error.empty()?"":" (") << error << (error.empty()?"":")") ) ;
144
145 // update the envelope forward-to-address
146 GStore::StoredFile msg( m_store , message_id , storestate() ) ;
147 msg.noUnlock() ;
148 msg.editEnvelope( [address](GStore::Envelope &env_){env_.forward_to_address=address;} ) ;
149
150 m_result = error.empty() ? Result::ok : Result::fail ;
151 m_timer.startTimer( 0U ) ;
152}
153
154GStore::FileStore::State GFilters::MxFilter::storestate() const
155{
156 return m_filter_type == GSmtp::Filter::Type::server ?
157 GStore::FileStore::State::New :
158 GStore::FileStore::State::Locked ;
159}
160
161GFilters::MxLookup::Config GFilters::MxFilter::mxconfig( const std::string & spec )
162{
163 MxLookup::Config config ;
164 G::string_view spec_sv = spec ;
165 for( G::StringTokenView t(spec_sv,";",1U) ; t ; ++t )
166 {
167 if( t().find("nst=") == 0U && t().size() > 4U )
168 config.ns_timeout = G::TimeInterval( G::Str::toUInt(t().substr(4U),"1") ) ;
169 else if( t().find("rt=") == 0U && t().size() > 3U )
170 config.restart_timeout = G::TimeInterval( G::Str::toUInt(t().substr(3U),"15") ) ;
171 }
172 return config ;
173}
174
175std::vector<GNet::Address> GFilters::MxFilter::mxnameservers( const std::string & spec )
176{
177 std::vector<GNet::Address> result ;
178 G::string_view spec_sv = spec ;
179 for( G::StringTokenView t(spec_sv,";",1U) ; t ; ++t )
180 {
181 if( GNet::Address::validString( G::sv_to_string(t()) ) )
182 result.push_back( GNet::Address::parse( G::sv_to_string(t()) ) ) ;
183 }
184 return result.empty() ? GNet::nameservers(53U) : result ;
185}
186
187std::string GFilters::MxFilter::parseForwardToDomain( const std::string & forward_to )
188{
189 return parseForwardTo(forward_to).first ;
190}
191
192unsigned int GFilters::MxFilter::parseForwardToPort( const std::string & forward_to )
193{
194 return parseForwardTo(forward_to).second ;
195}
196
197std::pair<std::string,unsigned int> GFilters::MxFilter::parseForwardTo( const std::string & forward_to )
198{
199 // "example.com:<port>"
200 // "example.com"
201 // "user@example.com:<port>"
202 // "user@example.com"
203 //
204 auto no_user = G::Str::tailView( forward_to , "@" , false ) ;
205 std::size_t pos = no_user.rfind( ':' ) ;
206 auto head = G::Str::headView( no_user , pos , no_user ) ;
207 auto tail = G::Str::tailView( no_user , pos , {} ) ;
208 bool with_port = !tail.empty() && G::Str::isNumeric( tail ) ;
209 auto first = with_port ? head : no_user ;
210 auto second = with_port ? G::Str::toUInt(tail) : 0U ;
211 return { G::sv_to_string(first) , second } ;
212}
213
214std::string GFilters::MxFilter::prefix() const
215{
216 return G::sv_to_string(strtype(m_filter_type)).append(" [").append(id()).append(1U,']') ;
217}
218
A concrete GSmtp::Filter class for message routing: if the message's 'forward-to' field is set then t...
Definition: gmxfilter.h:46
~MxFilter() override
Destructor.
Definition: gmxfilter.cpp:48
MxFilter(GNet::ExceptionSink es, GStore::FileStore &, Filter::Type, const Filter::Config &, const std::string &spec)
Constructor.
Definition: gmxfilter.cpp:32
static bool enabled()
Returns true if implemented.
Definition: gmxlookup.cpp:43
static Address parse(const std::string &display_string)
Factory function for any address family.
Definition: gaddress.cpp:178
static bool validString(const std::string &display_string, std::string *reason=nullptr)
Returns true if the transport-address display string is valid.
Definition: gaddress.cpp:385
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
A structure containing the contents of an envelope file, with support for file reading,...
Definition: genvelope.h:41
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:56
static Envelope readEnvelope(const G::Path &, std::ifstream *=nullptr)
Used by FileStore sibling classes to read an envelope file.
Definition: gfilestore.cpp:308
A somewhat opaque identifer for a GStore::MessageStore message id.
Definition: gmessagestore.h:43
std::string str() const
Returns the id string.
A concete class implementing the GStore::StoredMessage interface for separate envelope and content fi...
Definition: gstoredfile.h:52
A general-purpose exception class derived from std::exception and containing an error message.
Definition: gexception.h:64
A Path object represents a file system path.
Definition: gpath.h:73
static string_view headView(string_view in, std::size_t pos, string_view default_={}) noexcept
Like head() but returning a view into the input string.
Definition: gstr.cpp:1311
static string_view tailView(string_view in, std::size_t pos, string_view default_={}) noexcept
Like tail() but returning a view into the input string.
Definition: gstr.cpp:1340
static unsigned int toUInt(string_view s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:651
static bool isNumeric(string_view s, bool allow_minus_sign=false) noexcept
Returns true if every character is a decimal digit.
Definition: gstr.cpp:403
static bool headMatch(const std::string &in, string_view head) noexcept
Returns true if the string has the given start (or head is empty).
Definition: gstr.cpp:1362
A zero-copy string token iterator where the token separators are runs of whitespace characters,...
Definition: gstringtoken.h:54
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:299
A class like c++17's std::string_view.
Definition: gstringview.h:51
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:240
A configuration structure for GFilters::MxLookup.
Definition: gmxlookup.h:52