E-MailRelay

gmxlookup.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 gmxlookup.cpp
19///
20
21#include "gdef.h"
22#include "gmxlookup.h"
23#include "gdnsmessage.h"
24#include "gnameservers.h"
25#include "gstr.h"
26#include "gstringtoken.h"
27#include "gassert.h"
28#include "glog.h"
29#include <fstream>
30#include <vector>
31#include <utility>
32
33 namespace GFilters
34{
35 namespace MxLookupImp
36 {
37 enum class Result { error , fatal , mx , cname , ip } ;
38 std::pair<Result,std::string> parse( const GNet::DnsMessage & , const GNet::Address & , unsigned int ) ;
39 }
40}
41
42 bool GFilters::MxLookup::enabled()
43{
44 return true ;
45}
46
47#ifndef G_LIB_SMALL
48 GFilters::MxLookup::MxLookup( GNet::EventState es , Config config ) :
49 MxLookup(es,config,GNet::nameservers(53U))
50{
51}
52#endif
53
54 GFilters::MxLookup::MxLookup( GNet::EventState es , Config config ,
55 const std::vector<GNet::Address> & nameservers ) :
56 m_es(es) ,
57 m_config(config) ,
58 m_message_id(GStore::MessageId::none()) ,
59 m_ns_index(0U) ,
60 m_ns_failures(0U) ,
61 m_nameservers(nameservers) ,
62 m_timer(*this,&MxLookup::onTimeout,es)
63{
64 if( m_nameservers.empty() )
65 {
66 m_nameservers.push_back( GNet::Address::loopback( GNet::Address::Family::ipv4 , 53U ) ) ;
67 m_nameservers.push_back( GNet::Address::loopback( GNet::Address::Family::ipv6 , 53U ) ) ;
68 }
69
70 bool ipv4 = std::find_if( m_nameservers.begin() , m_nameservers.end() ,
71 [](const GNet::Address &a_){return a_.is4();} ) != m_nameservers.end() ;
72 if( ipv4 )
73 {
74 m_socket4 = std::make_unique<GNet::DatagramSocket>( GNet::Address::Family::ipv4 , 0 , GNet::DatagramSocket::Config() ) ;
75 m_socket4->addReadHandler( *this , m_es ) ;
76 G_DEBUG( "GFilters::MxLookup::ctor: ipv4 udp socket: " << (*m_socket4).getLocalAddress().displayString() ) ;
77 }
78
79 bool ipv6 = std::find_if( m_nameservers.begin() , m_nameservers.end() ,
80 [](const GNet::Address &a_){return a_.is6();} ) != m_nameservers.end() ;
81 if( ipv6 )
82 {
83 m_socket6 = std::make_unique<GNet::DatagramSocket>( GNet::Address::Family::ipv6 , 0 , GNet::DatagramSocket::Config() ) ;
84 m_socket6->addReadHandler( *this , m_es ) ;
85 G_DEBUG( "GFilters::MxLookup::ctor: ipv6 udp socket: " << (*m_socket6).getLocalAddress().displayString() ) ;
86 }
87}
88
89 void GFilters::MxLookup::start( const GStore::MessageId & message_id , const std::string & forward_to , unsigned int port )
90{
91 if( !m_socket4 && !m_socket6 )
92 {
93 fail( "no nameserver" ) ;
94 }
95 else if( forward_to.empty() )
96 {
97 fail( "invalid empty doman" ) ;
98 }
99 else
100 {
101 m_message_id = message_id ;
102 m_port = port ? port : 25U ;
103 m_ns_index = 0U ;
104 m_ns_failures = 0U ;
105 m_question = forward_to ;
106 sendMxQuestion( m_ns_index , m_question ) ;
107 startTimer() ;
108 }
109}
110
111 void GFilters::MxLookup::readEvent()
112{
113 G_DEBUG( "GFilters::MxLookup::readEvent" ) ;
114 std::vector<char> buffer( 4096U ) ; // 512 in RFC-1035 4.2.1
115 ssize_t nread = m_socket4->read( buffer.data() , buffer.size() ) ;
116 if( nread > 0 )
117 process( buffer.data() , static_cast<std::size_t>(nread) ) ;
118 else if( (nread=m_socket6->read( buffer.data() , buffer.size() )) > 0 ) // NOLINT assignment
119 process( buffer.data() , static_cast<std::size_t>(nread) ) ;
120 else
121 fail( "dns socket error" ) ;
122}
123
124 void GFilters::MxLookup::process( const char * p , std::size_t n )
125{
126 G_DEBUG( "GFilters::MxLookup::process: dns message size " << n ) ;
127 using namespace MxLookupImp ;
128 GNet::DnsMessage response( p , n ) ;
129 if( response.valid() && response.QR() && response.ID() && response.ID() < (m_nameservers.size()+1U) )
130 {
131 std::size_t ns_index = static_cast<std::size_t>(response.ID()) - 1U ;
132 auto pair = parse( response , m_nameservers.at(ns_index) , m_port ) ;
133 if( pair.first == Result::error && (m_ns_failures+1U) < m_nameservers.size() )
134 disable( ns_index , pair.second ) ;
135 else if( pair.first == Result::error || pair.first == Result::fatal )
136 fail( pair.second ) ;
137 else if( pair.first == Result::mx )
138 sendHostQuestion( ns_index , pair.second ) ;
139 else if( pair.first == Result::cname )
140 sendMxQuestion( ns_index , pair.second ) ;
141 else if( pair.first == Result::ip )
142 succeed( pair.second ) ;
143 }
144}
145
146 void GFilters::MxLookup::disable( std::size_t ns_index , const std::string & reason )
147{
148 G_LOG_MORE( "GFilters::MxLookup::disable: mx: nameserver "
149 << "[" << m_nameservers.at(ns_index).displayString() << "] disabled (" << reason << ")" ) ;
150 m_nameservers.at(ns_index) = GNet::Address::defaultAddress() ;
151 m_ns_failures++ ;
152}
153
154std::pair<GFilters::MxLookupImp::Result,std::string> GFilters::MxLookupImp::parse( const GNet::DnsMessage & response ,
155 const GNet::Address & ns_address , unsigned int port )
156{
157 G_ASSERT( port != 0U ) ;
158 std::string from = " from " + ns_address.hostPartString() ;
159 if( response.RCODE() == 3 && response.AA() )
160 {
161 return { Result::fatal , "rcode nxdomain" + from } ;
162 }
163 if( response.RCODE() != 0 )
164 {
165 return { Result::error , "rcode " + G::Str::fromUInt(response.RCODE()) + from } ;
166 }
167 else if( response.ANCOUNT() == 0U )
168 {
169 return { Result::error , "no answer section" + from } ;
170 }
171 else
172 {
173 GNet::Address address = GNet::Address::defaultAddress() ;
174 std::string cname_result ;
175 std::string mx_result ;
176 unsigned int mx_pr = 0U ;
177
178 unsigned int offset = response.QDCOUNT() ;
179 for( unsigned int i = 0 ; i < response.ANCOUNT() ; i++ )
180 {
181 auto rr = response.rr( i + offset ) ;
182 if( rr.isa("MX") )
183 {
184 unsigned int pr = rr.rdata().word( 0U ) ;
185 std::string name = rr.rdata().dname( 2U ) ;
186 G_LOG_MORE( "GFilters::MxLookupImp::parse: mx: answer: "
187 << "mx [" << name << "](priority " << pr << ")" << from ) ;
188 if( !name.empty() && ( mx_result.empty() || pr < mx_pr ) )
189 {
190 mx_pr = pr ;
191 mx_result = name ;
192 }
193 }
194 else if( rr.isa("CNAME") ) // RFC-974 p4
195 {
196 std::string cname = rr.rdata().dname( 0U ) ;
197 G_LOG_MORE( "GFilters::MxLookupImp::parse: mx: answer: "
198 << "cname [" << cname << "]" << from ) ;
199 cname_result = cname ;
200 }
201 else
202 {
203 GNet::Address a = rr.address( port , std::nothrow ) ;
204 G_LOG_MORE_IF( a.port() , "GFilters::MxLookupImp::parse: mx: answer: "
205 << "host-ip [" << a.hostPartString() << "]" << from ) ;
206 if( address.port() == 0U && a.port() != 0U )
207 address = a ;
208 }
209 }
210
211 if( !cname_result.empty() )
212 return { Result::cname , cname_result } ;
213 else if( address.port() != 0U )
214 return { Result::ip , address.displayString() } ;
215 else if( !mx_result.empty() )
216 return { Result::mx , mx_result } ;
217 else
218 return { Result::error , "invalid response" + from } ;
219 }
220}
221
222 void GFilters::MxLookup::sendMxQuestion( std::size_t ns_index , const std::string & mx_question )
223{
224 if( m_nameservers[ns_index] != GNet::Address::defaultAddress() )
225 {
226 G_LOG_MORE( "GFilters::MxLookup::sendMxQuestion: mx: question: mx [" << mx_question << "] "
227 << "to " << m_nameservers[ns_index].hostPartString()
228 << (m_nameservers[ns_index].port()==53U?"":(" port "+G::Str::fromUInt(m_nameservers[ns_index].port()))) ) ;
229 unsigned int id = static_cast<unsigned int>(ns_index) + 1U ;
230 GNet::DnsMessageRequest request( "MX" , mx_question , id ) ;
231 socket(ns_index).writeto( request.p() , request.n() , m_nameservers[ns_index] ) ;
232 }
233}
234
235 void GFilters::MxLookup::sendHostQuestion( std::size_t ns_index , const std::string & host_question )
236{
237 if( m_nameservers[ns_index] != GNet::Address::defaultAddress() )
238 {
239 G_LOG_MORE( "GFilters::MxLookup::sendHostQuestion: mx: question: host-ip [" << host_question << "] "
240 << "to " << m_nameservers[ns_index].hostPartString() ) ;
241 unsigned int id = static_cast<unsigned int>(ns_index) + 1U ;
242 GNet::DnsMessageRequest request( "A" , host_question , id ) ;
243 socket(ns_index).writeto( request.p() , request.n() , m_nameservers[ns_index] ) ;
244 }
245}
246
247 void GFilters::MxLookup::cancel()
248{
249 dropReadHandlers() ;
250 m_timer.cancelTimer() ;
251}
252
253 void GFilters::MxLookup::dropReadHandlers()
254{
255 if( m_socket4 )
256 m_socket4->dropReadHandler() ;
257 if( m_socket6 )
258 m_socket6->dropReadHandler() ;
259}
260
261 void GFilters::MxLookup::fail( const std::string & error )
262{
263 m_error = "mx: " + error ;
264 dropReadHandlers() ;
265 m_timer.startTimer( 0U ) ;
266}
267
268 void GFilters::MxLookup::onTimeout()
269{
270 if( !m_error.empty() )
271 {
272 cancel() ;
273 m_done_signal.emit( m_message_id , "" , m_error ) ;
274 }
275 else
276 {
277 m_ns_index++ ;
278 if( m_ns_index == m_nameservers.size() )
279 m_ns_index = 0U ;
280
281 sendMxQuestion( m_ns_index , m_question ) ;
282 startTimer() ;
283 }
284}
285
286 void GFilters::MxLookup::startTimer()
287{
288 bool last = (m_ns_index+1U) == m_nameservers.size() ;
289 G::TimeInterval timeout = last ? m_config.restart_timeout : m_config.ns_timeout ;
290 m_timer.startTimer( timeout ) ;
291}
292
293 void GFilters::MxLookup::succeed( const std::string & result )
294{
295 cancel() ;
296 m_done_signal.emit( m_message_id , result , "" ) ;
297}
298
299 GNet::DatagramSocket & GFilters::MxLookup::socket( std::size_t ns_index )
300{
301 return m_nameservers.at(ns_index).is4() ? *m_socket4 : *m_socket6 ;
302}
303
304 G::Slot::Signal<GStore::MessageId,std::string,std::string> & GFilters::MxLookup::doneSignal() noexcept
305{
306 return m_done_signal ;
307}
308
309GFilters::MxLookup::Config::Config()
310= default ;
311
GFilters::MxLookup
A DNS MX lookup client.
Definition: gmxlookup.h:50
GFilters::MxLookup::MxLookup
MxLookup(GNet::EventState, Config={})
Constructor.
Definition: gmxlookup.cpp:48
GFilters::MxLookup::doneSignal
G::Slot::Signal< GStore::MessageId, std::string, std::string > & doneSignal() noexcept
Returns a reference to the completion signal.
Definition: gmxlookup.cpp:304
GFilters::MxLookup::enabled
static bool enabled()
Returns true if implemented.
Definition: gmxlookup.cpp:42
GFilters::MxLookup::start
void start(const GStore::MessageId &, const std::string &question_domain, unsigned int port)
Starts the lookup.
Definition: gmxlookup.cpp:89
GFilters::MxLookup::cancel
void cancel()
Cancels the lookup so the doneSignal() is not emitted.
Definition: gmxlookup.cpp:247
GNet::Address
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:63
GNet::Address::loopback
static Address loopback(Family, unsigned int port=0U)
Returns a loopback address.
Definition: gaddress.cpp:208
GNet::Address::defaultAddress
static Address defaultAddress()
Returns a default address, being the IPv4 wildcard address with a zero port number.
Definition: gaddress.cpp:203
GNet::Address::displayString
std::string displayString(bool with_scope_id=false) const
Returns a printable string that represents the transport address.
Definition: gaddress.cpp:356
GNet::Address::port
unsigned int port() const
Returns port part of the address.
Definition: gaddress.cpp:435
GNet::Address::hostPartString
std::string hostPartString() const
Returns a printable string that represents the network address.
Definition: gaddress.cpp:365
GNet::Address::address
const sockaddr * address() const
Returns the sockaddr address.
Definition: gaddress.cpp:417
GNet::DatagramSocket
A derivation of GNet::Socket for a datagram socket.
Definition: gsocket.h:430
GNet::DnsMessageRRData::word
unsigned int word(unsigned int offset) const
Calls rdataWord().
Definition: gdnsmessage.h:424
GNet::DnsMessageRR::rdata
const DnsMessageRRData & rdata() const
Provides access to the message RDATA.
Definition: gdnsmessage.h:382
GNet::DnsMessageRequest
Represents a DNS query message.
Definition: gdnsmessage.h:358
GNet::DnsMessage
A DNS message parser, with static factory functions for message composition.
Definition: gdnsmessage.h:56
GNet::DnsMessage::ANCOUNT
unsigned int ANCOUNT() const
Returns the header ANCOUNT field, ie.
Definition: gdnsmessage.cpp:219
GNet::DnsMessage::RCODE
unsigned int RCODE() const
Returns the header RCODE.
Definition: gdnsmessage.cpp:209
GNet::DnsMessage::QDCOUNT
unsigned int QDCOUNT() const
Returns the header QDCOUNT field, ie.
Definition: gdnsmessage.cpp:214
GNet::DnsMessage::AA
bool AA() const
Returns the header AA flag (authorative).
Definition: gdnsmessage.cpp:178
GNet::DnsMessage::rr
RR rr(unsigned int n) const
Returns the n'th record as a RR record.
Definition: gdnsmessage.cpp:290
GNet::EventState
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
GStore::MessageId
A somewhat opaque identifer for a GStore::MessageStore message id.
Definition: gmessagestore.h:43
G::Str::fromUInt
static std::string fromUInt(unsigned int ui)
Converts unsigned int 'ui' to a string.
Definition: gstr.h:612
G::TimeInterval
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:305
gassert.h
gdnsmessage.h
glog.h
gmxlookup.h
gnameservers.h
gstr.h
gstringtoken.h
GFilters
Message filter classes.
Definition: gcopyfilter.h:31
GNet
Network classes.
Definition: gdef.h:1243
GStore
Message store classes.
Definition: genvelope.cpp:30
GFilters::MxLookup::Config
A configuration structure for GFilters::MxLookup.
Definition: gmxlookup.h:53
GNet::DatagramSocket::Config
A configuration structure for GNet::DatagramSocket.
Definition: gsocket.h:433
G::Slot::Signal< GStore::MessageId, std::string, std::string >