CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/net/dns/resolver.rb
Views: 1904
1
# -*- coding: binary -*-
2
#
3
# $Id: Resolver.rb,v 1.11 2006/07/30 16:55:35 bluemonk Exp $
4
#
5
6
7
8
require 'socket'
9
require 'timeout'
10
require 'ipaddr'
11
require 'logger'
12
require 'net/dns/packet'
13
require 'net/dns/resolver/timeouts'
14
15
alias old_send send
16
17
module Net # :nodoc:
18
module DNS
19
20
include Logger::Severity
21
22
# =Name
23
#
24
# Net::DNS::Resolver - DNS resolver class
25
#
26
# =Synopsis
27
#
28
# require 'net/dns/resolver'
29
#
30
# =Description
31
#
32
# The Net::DNS::Resolver class implements a complete DNS resolver written
33
# in pure Ruby, without a single C line of code. It has all of the
34
# tipical properties of an evoluted resolver, and a bit of OO which
35
# comes from having used Ruby.
36
#
37
# This project started as a porting of the Net::DNS Perl module,
38
# written by Martin Fuhr, but turned out (in the last months) to be
39
# an almost complete rewriting. Well, maybe some of the features of
40
# the Perl version are still missing, but guys, at least this is
41
# readable code!
42
#
43
# FIXME
44
#
45
# =Environment
46
#
47
# The Following Environment variables can also be used to configure
48
# the resolver:
49
#
50
# * +RES_NAMESERVERS+: A space-separated list of nameservers to query.
51
#
52
# # Bourne Shell
53
# $ RES_NAMESERVERS="192.168.1.1 192.168.2.2 192.168.3.3"
54
# $ export RES_NAMESERVERS
55
#
56
# # C Shell
57
# % setenv RES_NAMESERVERS "192.168.1.1 192.168.2.2 192.168.3.3"
58
#
59
# * +RES_SEARCHLIST+: A space-separated list of domains to put in the
60
# search list.
61
#
62
# # Bourne Shell
63
# $ RES_SEARCHLIST="example.com sub1.example.com sub2.example.com"
64
# $ export RES_SEARCHLIST
65
#
66
# # C Shell
67
# % setenv RES_SEARCHLIST "example.com sub1.example.com sub2.example.com"
68
#
69
# * +LOCALDOMAIN+: The default domain.
70
#
71
# # Bourne Shell
72
# $ LOCALDOMAIN=example.com
73
# $ export LOCALDOMAIN
74
#
75
# # C Shell
76
# % setenv LOCALDOMAIN example.com
77
#
78
# * +RES_OPTIONS+: A space-separated list of resolver options to set.
79
# Options that take values are specified as option:value.
80
#
81
# # Bourne Shell
82
# $ RES_OPTIONS="retrans:3 retry:2 debug"
83
# $ export RES_OPTIONS
84
#
85
# # C Shell
86
# % setenv RES_OPTIONS "retrans:3 retry:2 debug"
87
#
88
class Resolver
89
90
class NextNameserver < RuntimeError
91
end
92
93
# An hash with the defaults values of almost all the
94
# configuration parameters of a resolver object. See
95
# the description for each parameter to have an
96
# explanation of its usage.
97
Defaults = {
98
:config_file => "/etc/resolv.conf",
99
:log_file => $stdout,
100
:port => 53,
101
:searchlist => [],
102
:nameservers => [IPAddr.new("127.0.0.1")],
103
:domain => "",
104
:source_port => 0,
105
:source_address => IPAddr.new("0.0.0.0"),
106
:retry_interval => 5,
107
:retry_number => 4,
108
:recursive => true,
109
:defname => true,
110
:dns_search => true,
111
:use_tcp => false,
112
:ignore_truncated => false,
113
:packet_size => 512,
114
:tcp_timeout => TcpTimeout.new(120),
115
:udp_timeout => UdpTimeout.new(5)}
116
117
# Create a new resolver object.
118
#
119
# Argument +config+ can either be empty or be an hash with
120
# some configuration parameters. To know what each parameter
121
# do, look at the description of each.
122
# Some example:
123
#
124
# # Use the system defaults
125
# res = Net::DNS::Resolver.new
126
#
127
# # Specify a configuration file
128
# res = Net::DNS::Resolver.new(:config_file => '/my/dns.conf')
129
#
130
# # Set some option
131
# res = Net::DNS::Resolver.new(:nameservers => "172.16.1.1",
132
# :recursive => false,
133
# :retry => 10)
134
#
135
# ===Config file
136
#
137
# Net::DNS::Resolver uses a config file to read the usual
138
# values a resolver needs, such as nameserver list and
139
# domain names. On UNIX systems the defaults are read from the
140
# following files, in the order indicated:
141
#
142
# * /etc/resolv.conf
143
# * $HOME/.resolv.conf
144
# * ./.resolv.conf
145
#
146
# The following keywords are recognized in resolver configuration files:
147
#
148
# * domain: the default domain.
149
# * search: a space-separated list of domains to put in the search list.
150
# * nameserver: a space-separated list of nameservers to query.
151
#
152
# Files except for /etc/resolv.conf must be owned by the effective userid
153
# running the program or they won't be read. In addition, several environment
154
# variables can also contain configuration information; see Environment
155
# in the main description for Resolver class.
156
#
157
# On Windows Systems, an attempt is made to determine the system defaults
158
# using the registry. This is still a work in progress; systems with many
159
# dynamically configured network interfaces may confuse Net::DNS.
160
#
161
# You can include a configuration file of your own when creating a resolver
162
# object:
163
#
164
# # Use my own configuration file
165
# my $res = Net::DNS::Resolver->new(config_file => '/my/dns.conf');
166
#
167
# This is supported on both UNIX and Windows. Values pulled from a custom
168
# configuration file override the system's defaults, but can still be
169
# overridden by the other arguments to Resolver::new.
170
#
171
# Explicit arguments to Resolver::new override both the system's defaults
172
# and the values of the custom configuration file, if any.
173
#
174
# ===Parameters
175
#
176
# The following arguments to Resolver::new are supported:
177
#
178
# - nameservers: an array reference of nameservers to query.
179
# - searchlist: an array reference of domains.
180
# - recurse
181
# - debug
182
# - domain
183
# - port
184
# - srcaddr
185
# - srcport
186
# - tcp_timeout
187
# - udp_timeout
188
# - retrans
189
# - retry
190
# - usevc
191
# - stayopen
192
# - igntc
193
# - defnames
194
# - dnsrch
195
# - persistent_tcp
196
# - persistent_udp
197
# - dnssec
198
#
199
# For more information on any of these options, please consult the
200
# method of the same name.
201
#
202
# ===Disclaimer
203
#
204
# Part of the above documentation is taken from the one in the
205
# Net::DNS::Resolver Perl module.
206
#
207
def initialize(config = {})
208
raise ResolverArgumentError, "Argument has to be Hash" unless config.kind_of? Hash
209
# config.key_downcase!
210
@config = Defaults.merge config
211
@raw = false
212
213
# New logger facility
214
@logger = Logger.new(@config[:log_file])
215
@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
216
217
#------------------------------------------------------------
218
# Resolver configuration will be set in order from:
219
# 1) initialize arguments
220
# 2) ENV variables
221
# 3) config file
222
# 4) defaults (and /etc/resolv.conf for config)
223
#------------------------------------------------------------
224
225
226
227
#------------------------------------------------------------
228
# Parsing config file
229
#------------------------------------------------------------
230
parse_config_file
231
232
#------------------------------------------------------------
233
# Parsing ENV variables
234
#------------------------------------------------------------
235
parse_environment_variables
236
237
#------------------------------------------------------------
238
# Parsing arguments
239
#------------------------------------------------------------
240
config.each do |key,val|
241
next if key == :log_file or key == :config_file
242
begin
243
eval "self.#{key.to_s} = val"
244
rescue NoMethodError
245
raise ResolverArgumentError, "Option #{key} not valid"
246
end
247
end
248
end
249
250
# Get the resolver searchlist, returned as an array of entries
251
#
252
# res.searchlist
253
# #=> ["example.com","a.example.com","b.example.com"]
254
#
255
def searchlist
256
@config[:searchlist].deep_dup
257
end
258
259
# Set the resolver searchlist.
260
# +arg+ can be a single string or an array of strings
261
#
262
# res.searchstring = "example.com"
263
# res.searchstring = ["example.com","a.example.com","b.example.com"]
264
#
265
# Note that you can also append a new name to the searchlist
266
#
267
# res.searchlist << "c.example.com"
268
# res.searchlist
269
# #=> ["example.com","a.example.com","b.example.com","c.example.com"]
270
#
271
# The default is an empty array
272
#
273
def searchlist=(arg)
274
case arg
275
when String
276
@config[:searchlist] = [arg] if valid? arg
277
@logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
278
when Array
279
@config[:searchlist] = arg if arg.all? {|x| valid? x}
280
@logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
281
else
282
raise ResolverArgumentError, "Wrong argument format, neither String nor Array"
283
end
284
end
285
286
# Get the list of resolver nameservers, in a dotted decimal format
287
#
288
# res.nameservers
289
# #=> ["192.168.0.1","192.168.0.2"]
290
#
291
def nameservers
292
arr = []
293
@config[:nameservers].each do |x|
294
arr << x.to_s
295
end
296
arr
297
end
298
alias_method :nameserver, :nameservers
299
300
# Set the list of resolver nameservers
301
# +arg+ can be a single ip address or an array of addresses
302
#
303
# res.nameservers = "192.168.0.1"
304
# res.nameservers = ["192.168.0.1","192.168.0.2"]
305
#
306
# If you want you can specify the addresses as IPAddr instances
307
#
308
# ip = IPAddr.new("192.168.0.3")
309
# res.nameservers << ip
310
# #=> ["192.168.0.1","192.168.0.2","192.168.0.3"]
311
#
312
# The default is 127.0.0.1 (localhost)
313
#
314
def nameservers=(arg)
315
case arg
316
when String
317
begin
318
@config[:nameservers] = [IPAddr.new(arg)]
319
@logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
320
rescue ArgumentError # arg is in the name form, not IP
321
nameservers_from_name(arg)
322
end
323
when IPAddr
324
@config[:nameservers] = [arg]
325
@logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
326
when Array
327
@config[:nameservers] = []
328
arg.each do |x|
329
@config[:nameservers] << case x
330
when String
331
begin
332
IPAddr.new(x)
333
rescue ArgumentError
334
nameservers_from_name(arg)
335
return
336
end
337
when IPAddr
338
x
339
else
340
raise ResolverArgumentError, "Wrong argument format"
341
end
342
end
343
@logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
344
else
345
raise ResolverArgumentError, "Wrong argument format, neither String, Array nor IPAddr"
346
end
347
end
348
alias_method("nameserver=","nameservers=")
349
350
# Return a string with the default domain
351
#
352
def domain
353
@config[:domain].dup
354
end
355
356
# Set the domain for the query
357
#
358
def domain=(name)
359
@config[:domain] = name if valid? name
360
end
361
362
# Return the defined size of the packet
363
#
364
def packet_size
365
@config[:packet_size]
366
end
367
368
# Get the port number to which the resolver sends queries.
369
#
370
# puts "Sending queries to port #{res.port}"
371
#
372
def port
373
@config[:port]
374
end
375
376
# Set the port number to which the resolver sends queries. This can be useful
377
# for testing a nameserver running on a non-standard port.
378
#
379
# res.port = 10053
380
#
381
# The default is port 53.
382
#
383
def port=(num)
384
if (0..65535).include? num
385
@config[:port] = num
386
@logger.info "Port number changed to #{num}"
387
else
388
raise ResolverArgumentError, "Wrong port number #{num}"
389
end
390
end
391
392
# Get the value of the source port number
393
#
394
# puts "Sending queries using port #{res.source_port}"
395
#
396
def source_port
397
@config[:source_port]
398
end
399
alias srcport source_port
400
401
# Set the local source port from which the resolver sends its queries.
402
#
403
# res.source_port = 40000
404
#
405
# Note that if you want to set a port you need root privileges, as
406
# raw sockets will be used to generate packets. The class will then
407
# generate the exception ResolverPermissionError if you're not root.
408
#
409
# The default is 0, which means that the port will be chosen by the
410
# underlying layers.
411
#
412
def source_port=(num)
413
unless root?
414
raise ResolverPermissionError, "Are you root?"
415
end
416
if (0..65535).include?(num)
417
@config[:source_port] = num
418
else
419
raise ResolverArgumentError, "Wrong port number #{num}"
420
end
421
end
422
alias srcport= source_port=
423
424
# Get the local address from which the resolver sends queries
425
#
426
# puts "Sending queries using source address #{res.source_address}"
427
#
428
def source_address
429
@config[:source_address].to_s
430
end
431
alias srcaddr source_address
432
433
# Set the local source address from which the resolver sends its
434
# queries.
435
#
436
# res.source_address = "172.16.100.1"
437
# res.source_address = IPAddr.new("172.16.100.1")
438
#
439
# You can specify +arg+ as either a string containing the ip address
440
# or an instance of IPAddr class.
441
#
442
# Normally this can be used to force queries out a specific interface
443
# on a multi-homed host. In this case, you should of course need to
444
# know the addresses of the interfaces.
445
#
446
# Another way to use this option is for some kind of spoofing attacks
447
# towards weak nameservers, to probe the security of your network.
448
# This includes specifying ranged attacks such as DoS and others. For
449
# a paper on DNS security, checks http://www.marcoceresa.com/security/
450
#
451
# Note that if you want to set a non-binded source address you need
452
# root privileges, as raw sockets will be used to generate packets.
453
# The class will then generate an exception if you're not root.
454
#
455
# The default is 0.0.0.0, meaning any local address (chosen on routing
456
# needs).
457
#
458
def source_address=(addr)
459
unless addr.respond_to? :to_s
460
raise ResolverArgumentError, "Wrong address argument #{addr}"
461
end
462
463
begin
464
port = rand(64000)+1024
465
@logger.warn "Try to determine state of source address #{addr} with port #{port}"
466
a = TCPServer.new(addr.to_s,port)
467
rescue SystemCallError => e
468
case e.errno
469
when 98 # Port already in use!
470
@logger.warn "Port already in use"
471
retry
472
when 99 # Address is not valid: raw socket
473
@raw = true
474
@logger.warn "Using raw sockets"
475
else
476
raise SystemCallError, e
477
end
478
ensure
479
a.close
480
end
481
482
case addr
483
when String
484
@config[:source_address] = IPAddr.new(string)
485
@logger.info "Using new source address: #{@config[:source_address]}"
486
when IPAddr
487
@config[:source_address] = addr
488
@logger.info "Using new source address: #{@config[:source_address]}"
489
else
490
raise ArgumentError, "Unknown dest_address format"
491
end
492
end
493
alias srcaddr= source_address=
494
495
# Return the retrasmission interval (in seconds) the resolvers has
496
# been set on
497
#
498
def retry_interval
499
@config[:retry_interval]
500
end
501
alias retrans retry_interval
502
503
# Set the retrasmission interval in seconds. Default 5 seconds
504
#
505
def retry_interval=(num)
506
if num > 0
507
@config[:retry_interval] = num
508
@logger.info "Retransmission interval changed to #{num} seconds"
509
else
510
raise ResolverArgumentError, "Interval must be positive"
511
end
512
end
513
alias retrans= retry_interval=
514
515
# The number of times the resolver will try a query
516
#
517
# puts "Will try a max of #{res.retry_number} queries"
518
#
519
def retry_number
520
@config[:retry_number]
521
end
522
523
# Set the number of times the resolver will try a query.
524
# Default 4 times
525
#
526
def retry_number=(num)
527
if num.kind_of? Integer and num > 0
528
@config[:retry_number] = num
529
@logger.info "Retrasmissions number changed to #{num}"
530
else
531
raise ResolverArgumentError, "Retry value must be a positive integer"
532
end
533
end
534
alias_method('retry=', 'retry_number=')
535
536
# This method will return true if the resolver is configured to
537
# perform recursive queries.
538
#
539
# print "The resolver will perform a "
540
# print res.recursive? ? "" : "not "
541
# puts "recursive query"
542
#
543
def recursive?
544
@config[:recursive]
545
end
546
alias_method :recurse, :recursive?
547
alias_method :recursive, :recursive?
548
549
# Sets whether or not the resolver should perform recursive
550
# queries. Default is true.
551
#
552
# res.recursive = false # perform non-recursive query
553
#
554
def recursive=(bool)
555
case bool
556
when TrueClass,FalseClass
557
@config[:recursive] = bool
558
@logger.info("Recursive state changed to #{bool}")
559
else
560
raise ResolverArgumentError, "Argument must be boolean"
561
end
562
end
563
alias_method :recurse=, :recursive=
564
565
# Return a string representing the resolver state, suitable
566
# for printing on the screen.
567
#
568
# puts "Resolver state:"
569
# puts res.state
570
#
571
def state
572
str = ";; RESOLVER state:\n;; "
573
i = 1
574
@config.each do |key,val|
575
if key == :log_file or key == :config_file
576
str << "#{key}: #{val} \t"
577
else
578
str << "#{key}: #{eval(key.to_s)} \t"
579
end
580
str << "\n;; " if i % 2 == 0
581
i += 1
582
end
583
str
584
end
585
alias print state
586
alias inspect state
587
588
# Checks whether the +defname+ flag has been activate.
589
def defname?
590
@config[:defname]
591
end
592
alias defname defname?
593
594
# Set the flag +defname+ in a boolean state. if +defname+ is true,
595
# calls to Resolver#query will append the default domain to names
596
# that contain no dots.
597
# Example:
598
#
599
# # Domain example.com
600
# res.defname = true
601
# res.query("machine1")
602
# #=> This will perform a query for machine1.example.com
603
#
604
# Default is true.
605
#
606
def defname=(bool)
607
case bool
608
when TrueClass,FalseClass
609
@config[:defname] = bool
610
@logger.info("Defname state changed to #{bool}")
611
else
612
raise ResolverArgumentError, "Argument must be boolean"
613
end
614
end
615
616
# Get the state of the dns_search flag
617
def dns_search
618
@config[:dns_search]
619
end
620
alias_method :dnsrch, :dns_search
621
622
# Set the flag +dns_search+ in a boolean state. If +dns_search+
623
# is true, when using the Resolver#search method will be applied
624
# the search list. Default is true.
625
#
626
def dns_search=(bool)
627
case bool
628
when TrueClass,FalseClass
629
@config[:dns_search] = bool
630
@logger.info("DNS search state changed to #{bool}")
631
else
632
raise ResolverArgumentError, "Argument must be boolean"
633
end
634
end
635
alias_method("dnsrch=","dns_search=")
636
637
# Get the state of the use_tcp flag.
638
#
639
def use_tcp?
640
@config[:use_tcp]
641
end
642
alias_method :usevc, :use_tcp?
643
alias_method :use_tcp, :use_tcp?
644
645
# If +use_tcp+ is true, the resolver will perform all queries
646
# using TCP virtual circuits instead of UDP datagrams, which
647
# is the default for the DNS protocol.
648
#
649
# res.use_tcp = true
650
# res.query "host.example.com"
651
# #=> Sending TCP segments...
652
#
653
# Default is false.
654
#
655
def use_tcp=(bool)
656
case bool
657
when TrueClass,FalseClass
658
@config[:use_tcp] = bool
659
@logger.info("Use tcp flag changed to #{bool}")
660
else
661
raise ResolverArgumentError, "Argument must be boolean"
662
end
663
end
664
alias usevc= use_tcp=
665
666
def ignore_truncated?
667
@config[:ignore_truncated]
668
end
669
alias_method :ignore_truncated, :ignore_truncated?
670
671
def ignore_truncated=(bool)
672
case bool
673
when TrueClass,FalseClass
674
@config[:ignore_truncated] = bool
675
@logger.info("Ignore truncated flag changed to #{bool}")
676
else
677
raise ResolverArgumentError, "Argument must be boolean"
678
end
679
end
680
681
# Return an object representing the value of the stored TCP
682
# timeout the resolver will use in is queries. This object
683
# is an instance of the class +TcpTimeout+, and two methods
684
# are available for printing information: TcpTimeout#to_s
685
# and TcpTimeout#pretty_to_s.
686
#
687
# Here's some example:
688
#
689
# puts "Timeout of #{res.tcp_timeout} seconds" # implicit to_s
690
# #=> Timeout of 150 seconds
691
#
692
# puts "You set a timeout of " + res.tcp_timeout.pretty_to_s
693
# #=> You set a timeout of 2 minutes and 30 seconds
694
#
695
# If the timeout is infinite, a string "infinite" will
696
# be returned.
697
#
698
def tcp_timeout
699
@config[:tcp_timeout].to_s
700
end
701
702
# Set the value of TCP timeout for resolver queries that
703
# will be performed using TCP. A value of 0 means that
704
# the timeout will be infinite.
705
# The value is stored internally as a +TcpTimeout+ object, see
706
# the description for Resolver#tcp_timeout
707
#
708
# Default is 120 seconds
709
def tcp_timeout=(secs)
710
@config[:tcp_timeout] = TcpTimeout.new(secs)
711
@logger.info("New TCP timeout value: #{@config[:tcp_timeout]} seconds")
712
end
713
714
# Return an object representing the value of the stored UDP
715
# timeout the resolver will use in is queries. This object
716
# is an instance of the class +UdpTimeout+, and two methods
717
# are available for printing information: UdpTimeout#to_s
718
# and UdpTimeout#pretty_to_s.
719
#
720
# Here's some example:
721
#
722
# puts "Timeout of #{res.udp_timeout} seconds" # implicit to_s
723
# #=> Timeout of 150 seconds
724
#
725
# puts "You set a timeout of " + res.udp_timeout.pretty_to_s
726
# #=> You set a timeout of 2 minutes and 30 seconds
727
#
728
# If the timeout is zero, a string "not defined" will
729
# be returned.
730
#
731
def udp_timeout
732
@config[:udp_timeout].to_s
733
end
734
735
# Set the value of UDP timeout for resolver queries that
736
# will be performed using UDP. A value of 0 means that
737
# the timeout will not be used, and the resolver will use
738
# only +retry_number+ and +retry_interval+ parameters.
739
# That is the default.
740
#
741
# The value is stored internally as a +UdpTimeout+ object, see
742
# the description for Resolver#udp_timeout
743
#
744
def udp_timeout=(secs)
745
@config[:udp_timeout] = UdpTimeout.new(secs)
746
@logger.info("New UDP timeout value: #{@config[:udp_timeout]} seconds")
747
end
748
749
# Set a new log file for the logger facility of the resolver
750
# class. Could be a file descriptor too:
751
#
752
# res.log_file = $stderr
753
#
754
# Note that a new logging facility will be create, destroying
755
# the old one, which will then be impossibile to recover.
756
#
757
def log_file=(log)
758
@logger.close
759
@config[:log_file] = log
760
@logger = Logger.new(@config[:log_file])
761
@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
762
end
763
764
# This one permits to have a personal logger facility to handle
765
# resolver messages, instead of new built-in one, which is set up
766
# for a +$stdout+ (or +$stderr+) use.
767
#
768
# If you want your own logging facility you can create a new instance
769
# of the +Logger+ class:
770
#
771
# log = Logger.new("/tmp/resolver.log","weekly",2*1024*1024)
772
# log.level = Logger::DEBUG
773
# log.progname = "ruby_resolver"
774
#
775
# and then pass it to the resolver:
776
#
777
# res.logger = log
778
#
779
# Note that this will destroy the precedent logger.
780
#
781
def logger=(logger)
782
if logger.kind_of? Logger
783
@logger.close
784
@logger = logger
785
else
786
raise ResolverArgumentError, "Argument must be an instance of Logger class"
787
end
788
end
789
790
# Set the log level for the built-in logging facility.
791
#
792
# The log level can be one of the following:
793
#
794
# - +Net::DNS::DEBUG+
795
# - +Net::DNS::INFO+
796
# - +Net::DNS::WARN+
797
# - +Net::DNS::ERROR+
798
# - +Net::DNS::FATAL+
799
#
800
# Note that if the global variable $DEBUG is set (like when the
801
# -d switch is used at the command line) the logger level is
802
# automatically set at DEGUB.
803
#
804
# For further information, see Logger documentation in the
805
# Ruby standard library.
806
#
807
def log_level=(level)
808
@logger.level = level
809
end
810
811
# Performs a DNS query for the given name, applying the searchlist if
812
# appropriate. The search algorithm is as follows:
813
#
814
# 1. If the name contains at least one dot, try it as is.
815
# 2. If the name doesn't end in a dot then append each item in the search
816
# list to the name. This is only done if +dns_search+ is true.
817
# 3. If the name doesn't contain any dots, try it as is.
818
#
819
# The record type and class can be omitted; they default to +A+ and +IN+.
820
#
821
# packet = res.search('mailhost')
822
# packet = res.search('mailhost.example.com')
823
# packet = res.search('example.com', Net::DNS::MX)
824
# packet = res.search('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
825
#
826
# If the name is an IP address (Ipv4 or IPv6), in the form of a string
827
# or a +IPAddr+ object, then an appropriate PTR query will be performed:
828
#
829
# ip = IPAddr.new("172.16.100.2")
830
# packet = res.search(ip)
831
# packet = res.search("192.168.10.254")
832
#
833
# Returns a Net::DNS::Packet object. If you need to examine the response packet
834
# whether it contains any answers or not, use the send() method instead.
835
#
836
def search(name,type=Net::DNS::A,cls=Net::DNS::IN)
837
838
# If the name contains at least one dot then try it as is first.
839
if name.include? "."
840
@logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
841
ans = query(name,type,cls)
842
return ans if ans && ans.header && ans.header.anCount > 0
843
end
844
845
# If the name doesn't end in a dot then apply the search list.
846
if name !~ /\.$/ and @config[:dns_search]
847
@config[:searchlist].each do |domain|
848
newname = name + "." + domain
849
@logger.debug "Search(#{newname},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
850
ans = query(newname,type,cls)
851
return ans if ans && ans.header && ans.header.anCount > 0
852
end
853
end
854
855
# Finally, if the name has no dots then try it as is.
856
@logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
857
query(name+".",type,cls)
858
859
end
860
861
# Performs a DNS query for the given name; the search list
862
# is not applied. If the name doesn't contain any dots and
863
# +defname+ is true then the default domain will be appended.
864
#
865
# The record type and class can be omitted; they default to +A+
866
# and +IN+. If the name looks like an IP address (IPv4 or IPv6),
867
# then an appropriate PTR query will be performed.
868
#
869
# packet = res.query('mailhost')
870
# packet = res.query('mailhost.example.com')
871
# packet = res.query('example.com', Net::DNS::MX)
872
# packet = res.query('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
873
#
874
# If the name is an IP address (Ipv4 or IPv6), in the form of a string
875
# or a +IPAddr+ object, then an appropriate PTR query will be performed:
876
#
877
# ip = IPAddr.new("172.16.100.2")
878
# packet = res.query(ip)
879
# packet = res.query("192.168.10.254")
880
#
881
# Returns a Net::DNS::Packet object. If you need to examine the response
882
# packet whether it contains any answers or not, use the Resolver#send
883
# method instead.
884
#
885
def query(name,type=Net::DNS::A,cls=Net::DNS::IN)
886
887
# If the name doesn't contain any dots then append the default domain.
888
if name !~ /\./ and name !~ /:/ and @config[:defnames]
889
name += "." + @config[:domain]
890
end
891
892
@logger.debug "Query(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
893
begin
894
send(name,type,cls)
895
rescue ::NoResponseError
896
return
897
end
898
899
end
900
901
# Performs a DNS query for the given name. Neither the
902
# searchlist nor the default domain will be appended.
903
#
904
# The argument list can be either a Net::DNS::Packet object
905
# or a name string plus optional type and class, which if
906
# omitted default to +A+ and +IN+.
907
#
908
# Returns a Net::DNS::Packet object.
909
#
910
# # Sending a +Packet+ object
911
# send_packet = Net::DNS::Packet.new("host.example.com",Net::DNS::NS,Net::DNS::HS)
912
# packet = res.send(send_packet)
913
#
914
# # Performing a query
915
# packet = res.send("host.example.com")
916
# packet = res.send("host.example.com",Net::DNS::NS)
917
# packet = res.send("host.example.com",Net::DNS::NS,Net::DNS::HS)
918
#
919
# If the name is an IP address (Ipv4 or IPv6), in the form of a string
920
# or a IPAddr object, then an appropriate PTR query will be performed:
921
#
922
# ip = IPAddr.new("172.16.100.2")
923
# packet = res.send(ip)
924
# packet = res.send("192.168.10.254")
925
#
926
# Use +packet.header.ancount+ or +packet.answer+ to find out if there
927
# were any records in the answer section.
928
#
929
def send(argument,type=Net::DNS::A,cls=Net::DNS::IN)
930
if @config[:nameservers].size == 0
931
raise ResolverError, "No nameservers specified!"
932
end
933
934
method = :send_udp
935
936
if argument.kind_of? Net::DNS::Packet
937
packet = argument
938
else
939
packet = make_query_packet(argument,type,cls)
940
end
941
942
# Store packet_data for performance improvements,
943
# so methods don't keep on calling Packet#data
944
packet_data = packet.data
945
packet_size = packet_data.size
946
947
# Choose whether use TCP, UDP or RAW
948
if packet_size > @config[:packet_size] # Must use TCP, either plain or raw
949
if @raw # Use raw sockets?
950
@logger.info "Sending #{packet_size} bytes using TCP over RAW socket"
951
method = :send_raw_tcp
952
else
953
@logger.info "Sending #{packet_size} bytes using TCP"
954
method = :send_tcp
955
end
956
else # Packet size is inside the boundaries
957
if @raw # Use raw sockets?
958
@logger.info "Sending #{packet_size} bytes using UDP over RAW socket"
959
method = :send_raw_udp
960
elsif use_tcp? # User requested TCP
961
@logger.info "Sending #{packet_size} bytes using TCP"
962
method = :send_tcp
963
else # Finally use UDP
964
@logger.info "Sending #{packet_size} bytes using UDP"
965
end
966
end
967
968
if type == Net::DNS::AXFR
969
if @raw
970
@logger.warn "AXFR query, switching to TCP over RAW socket"
971
method = :send_raw_tcp
972
else
973
@logger.warn "AXFR query, switching to TCP"
974
method = :send_tcp
975
end
976
end
977
978
ans = self.old_send(method,packet,packet_data, nameservers.map {|ns| [ns, {}]})
979
980
unless ans
981
@logger.fatal "No response from nameservers list: aborting"
982
raise NoResponseError
983
end
984
985
@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
986
response = Net::DNS::Packet.parse(ans[0],ans[1])
987
988
if response.header.truncated? and not ignore_truncated?
989
@logger.warn "Packet truncated, retrying using TCP"
990
self.use_tcp = true
991
begin
992
return send(argument,type,cls)
993
ensure
994
self.use_tcp = false
995
end
996
end
997
998
return response
999
end
1000
1001
#
1002
# Performs a zone transfer for the zone passed as a parameter.
1003
#
1004
# Returns a list of Net::DNS::Packet (not answers!)
1005
#
1006
def axfr(name,cls=Net::DNS::IN)
1007
@logger.info "Requested AXFR transfer, zone #{name} class #{cls}"
1008
if @config[:nameservers].size == 0
1009
raise ResolverError, "No nameservers specified!"
1010
end
1011
1012
method = :send_tcp
1013
packet = make_query_packet(name, Net::DNS::AXFR, cls)
1014
1015
# Store packet_data for performance improvements,
1016
# so methods don't keep on calling Packet#data
1017
packet_data = packet.data
1018
packet_size = packet_data.size
1019
1020
if @raw
1021
@logger.warn "AXFR query, switching to TCP over RAW socket"
1022
method = :send_raw_tcp
1023
else
1024
@logger.warn "AXFR query, switching to TCP"
1025
method = :send_tcp
1026
end
1027
1028
answers = []
1029
soa = 0
1030
nameservers_and_hash = nameservers.map {|ns| [ns, {}]}
1031
self.old_send(method, packet, packet_data, nameservers_and_hash) do |ans|
1032
@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
1033
1034
begin
1035
return unless (response = Net::DNS::Packet.parse(ans[0],ans[1]))
1036
return if response.answer.empty?
1037
if response.answer[0].type == "SOA"
1038
soa += 1
1039
if soa >= 2
1040
break
1041
end
1042
end
1043
answers << response
1044
rescue NameError => e
1045
@logger.warn "Error parsing axfr response: #{e.message}"
1046
end
1047
end
1048
if answers.empty?
1049
@logger.fatal "No response from nameservers list: aborting"
1050
raise NoResponseError
1051
end
1052
1053
return answers
1054
end
1055
1056
#
1057
# Performs an MX query for the domain name passed as parameter.
1058
#
1059
# It actually uses the same methods a normal Resolver query would
1060
# use, but automatically sort the results based on preferences
1061
# and returns an ordered array.
1062
#
1063
# Example:
1064
#
1065
# res = Net::DNS::Resolver.new
1066
# res.mx("google.com")
1067
#
1068
def mx(name,cls=Net::DNS::IN)
1069
arr = []
1070
send(name, Net::DNS::MX, cls).answer.each do |entry|
1071
arr << entry if entry.type == 'MX'
1072
end
1073
return arr.sort_by {|a| a.preference}
1074
end
1075
1076
private
1077
1078
# Parse a configuration file specified as the argument.
1079
#
1080
def parse_config_file
1081
if RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/
1082
require 'win32/resolv'
1083
arr = Win32::Resolv.get_resolv_info
1084
self.domain = arr[0]
1085
self.nameservers = arr[1]
1086
else
1087
begin
1088
IO.foreach(@config[:config_file]) do |line|
1089
line.gsub!(/\s*[;#].*/,"")
1090
next unless line =~ /\S/
1091
case line
1092
when /^\s*domain\s+(\S+)/
1093
self.domain = $1
1094
when /^\s*search\s+(.*)/
1095
self.searchlist = $1.split(" ")
1096
when /^\s*nameserver\s+(.*)/
1097
$1.split(/\s+/).each do |nameserver|
1098
# per https://man7.org/linux/man-pages/man5/resolv.conf.5.html nameserver values must be IP addresses
1099
begin
1100
ip_addr = IPAddr.new(nameserver)
1101
rescue IPAddr::InvalidAddressError
1102
@logger.warn "Ignoring invalid name server '#{nameserver}' from configuration file"
1103
next
1104
else
1105
self.nameservers += [ip_addr]
1106
end
1107
end
1108
end
1109
end
1110
rescue => e
1111
@logger.error(e)
1112
end
1113
end
1114
end
1115
1116
# Parse environment variables
1117
def parse_environment_variables
1118
if ENV['RES_NAMESERVERS']
1119
self.nameservers = ENV['RES_NAMESERVERS'].split(" ")
1120
end
1121
if ENV['RES_SEARCHLIST']
1122
self.searchlist = ENV['RES_SEARCHLIST'].split(" ")
1123
end
1124
if ENV['LOCALDOMAIN']
1125
self.domain = ENV['LOCALDOMAIN']
1126
end
1127
if ENV['RES_OPTIONS']
1128
ENV['RES_OPTIONS'].split(" ").each do |opt|
1129
name,val = opt.split(":")
1130
begin
1131
eval("self.#{name} = #{val}")
1132
rescue NoMethodError
1133
raise ResolverArgumentError, "Invalid ENV option #{name}"
1134
end
1135
end
1136
end
1137
end
1138
1139
def nameservers_from_name(arg)
1140
arr = []
1141
arg.split(" ").each do |name|
1142
Resolver.new.search(name).each_address do |ip|
1143
arr << ip
1144
end
1145
end
1146
@config[:nameservers] << arr
1147
end
1148
1149
def make_query_packet(string,type,cls)
1150
case string
1151
when IPAddr
1152
name = string.reverse
1153
type = Net::DNS::PTR
1154
@logger.warn "PTR query required for address #{string}, changing type to PTR"
1155
when /\d/ # Contains a number, try to see if it's an IP or IPv6 address
1156
begin
1157
name = IPAddr.new(string).reverse
1158
type = Net::DNS::PTR
1159
rescue ArgumentError
1160
name = string if valid? string
1161
end
1162
else
1163
name = string if valid? string
1164
end
1165
1166
# Create the packet
1167
packet = Net::DNS::Packet.new(name,type,cls)
1168
1169
if packet.query?
1170
packet.header.recursive = @config[:recursive] ? 1 : 0
1171
end
1172
1173
# DNSSEC and TSIG stuff to be inserted here
1174
1175
packet
1176
1177
end
1178
1179
def send_tcp(packet,packet_data, nameservers)
1180
1181
ans = nil
1182
length = [packet_data.size].pack("n")
1183
1184
nameservers.each do |ns, _unused|
1185
begin
1186
socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)
1187
socket.bind(Socket.pack_sockaddr_in(@config[:source_port],@config[:source_address].to_s))
1188
1189
sockaddr = Socket.pack_sockaddr_in(@config[:port],ns.to_s)
1190
1191
@config[:tcp_timeout].timeout do
1192
socket.connect(sockaddr)
1193
@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
1194
socket.write(length+packet_data)
1195
got_something = false
1196
loop do
1197
buffer = ""
1198
begin
1199
ans = socket.recv(Net::DNS::INT16SZ)
1200
rescue ::Errno::ECONNRESET
1201
ans = ""
1202
end
1203
if ans.size == 0
1204
if got_something
1205
break #Proper exit from loop
1206
else
1207
@logger.warn "Connection reset to nameserver #{ns}, trying next."
1208
raise NextNameserver
1209
end
1210
end
1211
got_something = true
1212
len = ans.unpack("n")[0]
1213
1214
@logger.info "Receiving #{len} bytes..."
1215
1216
if len == 0
1217
@logger.warn "Receiving 0 length packet from nameserver #{ns}, trying next."
1218
raise NextNameserver
1219
end
1220
1221
while (buffer.size < len)
1222
left = len - buffer.size
1223
temp,from = socket.recvfrom(left)
1224
buffer += temp
1225
end
1226
1227
unless buffer.size == len
1228
@logger.warn "Malformed packet from nameserver #{ns}, trying next."
1229
raise NextNameserver
1230
end
1231
if block_given?
1232
yield [buffer,["",@config[:port],ns.to_s,ns.to_s]]
1233
break
1234
else
1235
return [buffer,["",@config[:port],ns.to_s,ns.to_s]]
1236
end
1237
end
1238
end
1239
rescue NextNameserver
1240
next
1241
rescue Timeout::Error
1242
@logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one"
1243
next
1244
ensure
1245
socket.close
1246
end
1247
end
1248
return nil
1249
end
1250
1251
def send_udp(packet, packet_data, nameservers)
1252
socket = UDPSocket.new
1253
socket.bind(@config[:source_address].to_s,@config[:source_port])
1254
1255
ans = nil
1256
response = ""
1257
nameservers.each do |ns, _unused|
1258
begin
1259
@config[:udp_timeout].timeout do
1260
@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
1261
socket.send(packet_data,0,ns.to_s,@config[:port])
1262
ans = socket.recvfrom(@config[:packet_size])
1263
end
1264
break if ans
1265
rescue Timeout::Error
1266
@logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one"
1267
next
1268
end
1269
end
1270
ans
1271
end
1272
1273
def valid?(name)
1274
if name =~ /[^-\w\.]/
1275
raise ResolverArgumentError, "Invalid domain name #{name}"
1276
else
1277
true
1278
end
1279
end
1280
1281
end # class Resolver
1282
end # module DNS
1283
end # module Net
1284
1285
class ResolverError < ArgumentError # :nodoc:
1286
end
1287
class ResolverArgumentError < ArgumentError # :nodoc:
1288
end
1289
class NoResponseError < StandardError # :nodoc:
1290
end
1291
1292
module ExtendHash # :nodoc:
1293
# Returns an hash with all the
1294
# keys turned into downcase
1295
#
1296
# hsh = {"Test" => 1, "FooBar" => 2}
1297
# hsh.key_downcase!
1298
# #=> {"test"=>1,"foobar"=>2}
1299
#
1300
def key_downcase!
1301
hsh = Hash.new
1302
self.each do |key,val|
1303
hsh[key.downcase] = val
1304
end
1305
self.replace(hsh)
1306
end
1307
end
1308
1309
class Hash # :nodoc:
1310
include ExtendHash
1311
end
1312
1313