bluemonk-net-dns 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+