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