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
33namespace 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
43{
44 return true ;
45}
46
47#ifndef G_LIB_SMALL
49 MxLookup(es,config,GNet::nameservers(53U))
50{
51}
52#endif
53
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
89void 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
111void 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
124void 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
146void 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 {
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
222void 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
235void 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
248{
249 dropReadHandlers() ;
250 m_timer.cancelTimer() ;
251}
252
253void GFilters::MxLookup::dropReadHandlers()
254{
255 if( m_socket4 )
256 m_socket4->dropReadHandler() ;
257 if( m_socket6 )
258 m_socket6->dropReadHandler() ;
259}
260
261void GFilters::MxLookup::fail( const std::string & error )
262{
263 m_error = "mx: " + error ;
264 dropReadHandlers() ;
265 m_timer.startTimer( 0U ) ;
266}
267
268void 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
286void 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
293void GFilters::MxLookup::succeed( const std::string & result )
294{
295 cancel() ;
296 m_done_signal.emit( m_message_id , result , "" ) ;
297}
298
299GNet::DatagramSocket & GFilters::MxLookup::socket( std::size_t ns_index )
300{
301 return m_nameservers.at(ns_index).is4() ? *m_socket4 : *m_socket6 ;
302}
303
305{
306 return m_done_signal ;
307}
308
309GFilters::MxLookup::Config::Config()
310= default ;
311
A DNS MX lookup client.
Definition: gmxlookup.h:50
MxLookup(GNet::EventState, Config={})
Constructor.
Definition: gmxlookup.cpp:48
G::Slot::Signal< GStore::MessageId, std::string, std::string > & doneSignal() noexcept
Returns a reference to the completion signal.
Definition: gmxlookup.cpp:304
static bool enabled()
Returns true if implemented.
Definition: gmxlookup.cpp:42
void start(const GStore::MessageId &, const std::string &question_domain, unsigned int port)
Starts the lookup.
Definition: gmxlookup.cpp:89
void cancel()
Cancels the lookup so the doneSignal() is not emitted.
Definition: gmxlookup.cpp:247
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:63
static Address loopback(Family, unsigned int port=0U)
Returns a loopback address.
Definition: gaddress.cpp:208
static Address defaultAddress()
Returns a default address, being the IPv4 wildcard address with a zero port number.
Definition: gaddress.cpp:203
std::string displayString(bool with_scope_id=false) const
Returns a printable string that represents the transport address.
Definition: gaddress.cpp:356
unsigned int port() const
Returns port part of the address.
Definition: gaddress.cpp:435
std::string hostPartString() const
Returns a printable string that represents the network address.
Definition: gaddress.cpp:365
const sockaddr * address() const
Returns the sockaddr address.
Definition: gaddress.cpp:417
A derivation of GNet::Socket for a datagram socket.
Definition: gsocket.h:430
unsigned int word(unsigned int offset) const
Calls rdataWord().
Definition: gdnsmessage.h:424
const DnsMessageRRData & rdata() const
Provides access to the message RDATA.
Definition: gdnsmessage.h:382
Represents a DNS query message.
Definition: gdnsmessage.h:358
A DNS message parser, with static factory functions for message composition.
Definition: gdnsmessage.h:56
unsigned int ANCOUNT() const
Returns the header ANCOUNT field, ie.
unsigned int RCODE() const
Returns the header RCODE.
unsigned int QDCOUNT() const
Returns the header QDCOUNT field, ie.
bool AA() const
Returns the header AA flag (authorative).
RR rr(unsigned int n) const
Returns the n'th record as a RR record.
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
A somewhat opaque identifer for a GStore::MessageStore message id.
Definition: gmessagestore.h:43
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
Message filter classes.
Definition: gcopyfilter.h:31
Network classes.
Definition: gdef.h:1243
Message store classes.
Definition: genvelope.cpp:30
A configuration structure for GFilters::MxLookup.
Definition: gmxlookup.h:53
A configuration structure for GNet::DatagramSocket.
Definition: gsocket.h:433