bluemonk-net-dns 0.5.0

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