dnsruby 1.55 → 1.56.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.
- checksums.yaml +4 -4
- data/README.md +96 -0
- data/Rakefile +30 -29
- data/demo/axfr.rb +93 -93
- data/demo/check_soa.rb +99 -99
- data/demo/check_zone.rb +59 -59
- data/demo/digdlv.rb +43 -43
- data/demo/digroot.rb +34 -34
- data/demo/example_recurse.rb +14 -14
- data/demo/mresolv.rb +30 -30
- data/demo/mx.rb +31 -31
- data/demo/rubydig.rb +37 -37
- data/demo/to_resolve.txt +3088 -3088
- data/demo/trace_dns.rb +46 -46
- data/lib/dnsruby.rb +161 -526
- data/lib/dnsruby/DNS.rb +305 -0
- data/lib/{Dnsruby/Cache.rb → dnsruby/cache.rb} +152 -152
- data/lib/{Dnsruby → dnsruby}/code_mapper.rb +48 -52
- data/lib/dnsruby/code_mappers.rb +295 -0
- data/lib/{Dnsruby/Config.rb → dnsruby/config.rb} +454 -454
- data/lib/{Dnsruby → dnsruby}/dnssec.rb +91 -91
- data/lib/{Dnsruby/Hosts.rb → dnsruby/hosts.rb} +125 -125
- data/lib/{Dnsruby → dnsruby}/ipv4.rb +26 -26
- data/lib/{Dnsruby → dnsruby}/ipv6.rb +42 -42
- data/lib/{Dnsruby → dnsruby}/key_cache.rb +29 -29
- data/lib/dnsruby/message/decoder.rb +164 -0
- data/lib/dnsruby/message/encoder.rb +75 -0
- data/lib/dnsruby/message/header.rb +249 -0
- data/lib/dnsruby/message/message.rb +629 -0
- data/lib/dnsruby/message/question.rb +86 -0
- data/lib/dnsruby/message/section.rb +96 -0
- data/lib/{Dnsruby → dnsruby}/name.rb +141 -141
- data/lib/dnsruby/packet_sender.rb +661 -0
- data/lib/{Dnsruby/Recursor.rb → dnsruby/recursor.rb} +235 -233
- data/lib/dnsruby/resolv.rb +113 -0
- data/lib/dnsruby/resolver.rb +1192 -0
- data/lib/dnsruby/resource/A.rb +56 -0
- data/lib/dnsruby/resource/AAAA.rb +54 -0
- data/lib/{Dnsruby → dnsruby}/resource/AFSDB.rb +68 -68
- data/lib/{Dnsruby → dnsruby}/resource/CERT.rb +105 -105
- data/lib/{Dnsruby → dnsruby}/resource/DHCID.rb +54 -54
- data/lib/dnsruby/resource/DLV.rb +27 -0
- data/lib/{Dnsruby → dnsruby}/resource/DNSKEY.rb +372 -372
- data/lib/{Dnsruby → dnsruby}/resource/DS.rb +255 -255
- data/lib/{Dnsruby → dnsruby}/resource/HINFO.rb +71 -71
- data/lib/{Dnsruby → dnsruby}/resource/HIP.rb +29 -29
- data/lib/{Dnsruby → dnsruby}/resource/IN.rb +30 -30
- data/lib/{Dnsruby → dnsruby}/resource/IPSECKEY.rb +31 -31
- data/lib/{Dnsruby → dnsruby}/resource/ISDN.rb +62 -62
- data/lib/{Dnsruby → dnsruby}/resource/KX.rb +65 -65
- data/lib/{Dnsruby → dnsruby}/resource/LOC.rb +263 -263
- data/lib/{Dnsruby → dnsruby}/resource/MINFO.rb +69 -69
- data/lib/{Dnsruby → dnsruby}/resource/MX.rb +65 -65
- data/lib/{Dnsruby → dnsruby}/resource/NAPTR.rb +98 -98
- data/lib/{Dnsruby → dnsruby}/resource/NSAP.rb +171 -171
- data/lib/dnsruby/resource/NSEC.rb +275 -0
- data/lib/dnsruby/resource/NSEC3.rb +332 -0
- data/lib/dnsruby/resource/NSEC3PARAM.rb +135 -0
- data/lib/dnsruby/resource/OPT.rb +272 -0
- data/lib/{Dnsruby → dnsruby}/resource/PX.rb +70 -70
- data/lib/{Dnsruby → dnsruby}/resource/RP.rb +75 -75
- data/lib/dnsruby/resource/RR.rb +421 -0
- data/lib/dnsruby/resource/RRSIG.rb +275 -0
- data/lib/dnsruby/resource/RRSet.rb +190 -0
- data/lib/{Dnsruby → dnsruby}/resource/RT.rb +67 -67
- data/lib/{Dnsruby → dnsruby}/resource/SOA.rb +94 -94
- data/lib/dnsruby/resource/SPF.rb +29 -0
- data/lib/dnsruby/resource/SRV.rb +112 -0
- data/lib/{Dnsruby → dnsruby}/resource/SSHFP.rb +14 -14
- data/lib/dnsruby/resource/TKEY.rb +163 -0
- data/lib/dnsruby/resource/TSIG.rb +593 -0
- data/lib/{Dnsruby → dnsruby}/resource/TXT.rb +191 -191
- data/lib/dnsruby/resource/X25.rb +55 -0
- data/lib/{Dnsruby → dnsruby}/resource/domain_name.rb +25 -25
- data/lib/{Dnsruby → dnsruby}/resource/generic.rb +80 -80
- data/lib/dnsruby/resource/resource.rb +25 -0
- data/lib/{Dnsruby → dnsruby}/select_thread.rb +148 -148
- data/lib/{Dnsruby/SingleResolver.rb → dnsruby/single_resolver.rb} +60 -60
- data/lib/{Dnsruby → dnsruby}/single_verifier.rb +344 -344
- data/lib/dnsruby/the_log.rb +44 -0
- data/lib/dnsruby/update.rb +278 -0
- data/lib/dnsruby/validator_thread.rb +124 -0
- data/lib/dnsruby/version.rb +3 -0
- data/lib/{Dnsruby → dnsruby}/zone_reader.rb +93 -93
- data/lib/{Dnsruby → dnsruby}/zone_transfer.rb +377 -377
- data/test/spec_helper.rb +16 -0
- data/test/tc_axfr.rb +31 -34
- data/test/tc_cache.rb +32 -32
- data/test/tc_dlv.rb +28 -28
- data/test/tc_dns.rb +73 -76
- data/test/tc_dnskey.rb +31 -32
- data/test/tc_dnsruby.rb +50 -44
- data/test/tc_ds.rb +36 -36
- data/test/tc_escapedchars.rb +252 -255
- data/test/tc_hash.rb +17 -21
- data/test/tc_header.rb +48 -57
- data/test/tc_hip.rb +19 -22
- data/test/tc_ipseckey.rb +18 -21
- data/test/tc_keith.rb +300 -0
- data/test/tc_message.rb +87 -0
- data/test/tc_misc.rb +83 -87
- data/test/tc_name.rb +81 -84
- data/test/tc_naptr.rb +18 -21
- data/test/tc_nsec.rb +55 -55
- data/test/tc_nsec3.rb +23 -24
- data/test/tc_nsec3param.rb +20 -21
- data/test/tc_packet.rb +90 -93
- data/test/tc_packet_unique_push.rb +48 -51
- data/test/tc_question.rb +30 -33
- data/test/tc_queue.rb +16 -17
- data/test/tc_recur.rb +16 -17
- data/test/tc_res_config.rb +38 -41
- data/test/tc_res_env.rb +29 -32
- data/test/tc_res_file.rb +26 -29
- data/test/tc_res_opt.rb +62 -65
- data/test/tc_resolver.rb +287 -242
- data/test/tc_rr-opt.rb +70 -63
- data/test/tc_rr-txt.rb +68 -71
- data/test/tc_rr-unknown.rb +45 -48
- data/test/tc_rr.rb +76 -70
- data/test/tc_rrset.rb +21 -22
- data/test/tc_rrsig.rb +19 -20
- data/test/tc_single_resolver.rb +294 -297
- data/test/tc_soak.rb +199 -202
- data/test/tc_soak_base.rb +29 -34
- data/test/tc_sshfp.rb +20 -23
- data/test/tc_tcp.rb +32 -35
- data/test/tc_tkey.rb +41 -44
- data/test/tc_tsig.rb +81 -84
- data/test/tc_update.rb +108 -111
- data/test/tc_validator.rb +29 -29
- data/test/tc_verifier.rb +81 -82
- data/test/ts_dnsruby.rb +16 -15
- data/test/ts_offline.rb +62 -63
- data/test/ts_online.rb +115 -115
- metadata +155 -90
- data/README +0 -59
- data/lib/Dnsruby/DNS.rb +0 -305
- data/lib/Dnsruby/PacketSender.rb +0 -656
- data/lib/Dnsruby/Resolver.rb +0 -1189
- data/lib/Dnsruby/TheLog.rb +0 -44
- data/lib/Dnsruby/message.rb +0 -1230
- data/lib/Dnsruby/resource/A.rb +0 -56
- data/lib/Dnsruby/resource/AAAA.rb +0 -54
- data/lib/Dnsruby/resource/DLV.rb +0 -27
- data/lib/Dnsruby/resource/NSEC.rb +0 -298
- data/lib/Dnsruby/resource/NSEC3.rb +0 -340
- data/lib/Dnsruby/resource/NSEC3PARAM.rb +0 -135
- data/lib/Dnsruby/resource/OPT.rb +0 -213
- data/lib/Dnsruby/resource/RRSIG.rb +0 -275
- data/lib/Dnsruby/resource/SPF.rb +0 -29
- data/lib/Dnsruby/resource/SRV.rb +0 -112
- data/lib/Dnsruby/resource/TKEY.rb +0 -163
- data/lib/Dnsruby/resource/TSIG.rb +0 -593
- data/lib/Dnsruby/resource/X25.rb +0 -55
- data/lib/Dnsruby/resource/resource.rb +0 -678
- data/lib/Dnsruby/update.rb +0 -278
- data/lib/Dnsruby/validator_thread.rb +0 -124
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# Superclass for all Dnsruby resource records.
|
|
2
|
+
#
|
|
3
|
+
# Represents a DNS RR (resource record) [RFC1035, section 3.2]
|
|
4
|
+
#
|
|
5
|
+
# Use Dnsruby::RR::create(...) to create a new RR record.
|
|
6
|
+
#
|
|
7
|
+
# mx = Dnsruby::RR.create("example.com. 7200 MX 10 mailhost.example.com.")
|
|
8
|
+
#
|
|
9
|
+
# rr = Dnsruby::RR.create({:name => "example.com", :type => "MX", :ttl => 7200,
|
|
10
|
+
# :preference => 10, :exchange => "mailhost.example.com"})
|
|
11
|
+
#
|
|
12
|
+
# s = rr.to_s # Get a String representation of the RR (in zone file format)
|
|
13
|
+
# rr_again = Dnsruby::RR.create(s)
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
require 'dnsruby/code_mappers'
|
|
17
|
+
|
|
18
|
+
module Dnsruby
|
|
19
|
+
class RR
|
|
20
|
+
|
|
21
|
+
include Comparable
|
|
22
|
+
|
|
23
|
+
def <=>(other)
|
|
24
|
+
# return 1 if ((!other) || !(other.name) || !(other.type))
|
|
25
|
+
# return -1 if (!@name)
|
|
26
|
+
if @name.canonical == other.name.canonical
|
|
27
|
+
@type.code != other.type.code ? (@type.code <=> other.type.code) : (@rdata <=> other.rdata)
|
|
28
|
+
else
|
|
29
|
+
@name <=> other.name
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A regular expression which catches any valid resource record.
|
|
34
|
+
@@RR_REGEX = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s*(#{Classes.regexp +
|
|
35
|
+
"|CLASS\\d+"})?\\s*(#{Types.regexp + '|TYPE\\d+'})?\\s*([\\s\\S]*)\$") #:nodoc: all
|
|
36
|
+
|
|
37
|
+
@@implemented_rr_map = nil
|
|
38
|
+
|
|
39
|
+
# The Resource's domain name
|
|
40
|
+
attr_reader :name
|
|
41
|
+
|
|
42
|
+
# The Resource type
|
|
43
|
+
attr_reader :type
|
|
44
|
+
|
|
45
|
+
# The Resource class
|
|
46
|
+
attr_reader :klass
|
|
47
|
+
|
|
48
|
+
# The Resource Time-To-Live
|
|
49
|
+
attr_accessor :ttl
|
|
50
|
+
|
|
51
|
+
# The Resource data section
|
|
52
|
+
attr_accessor :rdata
|
|
53
|
+
|
|
54
|
+
def rdlength
|
|
55
|
+
rdata.length
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def name=(new_name)
|
|
59
|
+
@name = new_name.kind_of?(Name) ? new_name : Name.create(new_name)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def type=(type)
|
|
63
|
+
@type = Types.new(type)
|
|
64
|
+
end
|
|
65
|
+
alias :rr_type :type
|
|
66
|
+
|
|
67
|
+
def klass=(klass)
|
|
68
|
+
if @type != Types::OPT
|
|
69
|
+
@klass = Classes.new(klass)
|
|
70
|
+
else
|
|
71
|
+
@klass = klass.is_a?(Classes) ? klass : Classes.new("CLASS#{klass}")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def clone
|
|
76
|
+
encoded = MessageEncoder.new { |encoder| encoder.put_rr(self, true) }.to_s
|
|
77
|
+
MessageDecoder.new(encoded).get_rr
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Determines if two Records could be part of the same RRset.
|
|
82
|
+
# This compares the name, type, and class of the Records; the ttl and
|
|
83
|
+
# rdata are not compared.
|
|
84
|
+
def sameRRset(rec)
|
|
85
|
+
if @klass != rec.klass || @name.downcase != rec.name.downcase
|
|
86
|
+
return false
|
|
87
|
+
elsif (rec.type == Types.RRSIG) && (@type == Types.RRSIG)
|
|
88
|
+
return rec.type_covered == self.type_covered
|
|
89
|
+
end
|
|
90
|
+
[rec, self].each do |rr|
|
|
91
|
+
if rr.type == Types::RRSIG
|
|
92
|
+
return (@type == rr.type_covered) || (rec.type == rr.type_covered)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
@type == rec.type
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def init_defaults
|
|
99
|
+
# Default to do nothing
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
def initialize(*args) #:nodoc: all
|
|
104
|
+
init_defaults
|
|
105
|
+
if args.length > 0
|
|
106
|
+
if args[0].class == Hash
|
|
107
|
+
from_hash(args[0])
|
|
108
|
+
return
|
|
109
|
+
else
|
|
110
|
+
@rdata = args[0]
|
|
111
|
+
# print "Loading RR from #{args[0]}, class : #{args[0].class}\n"
|
|
112
|
+
if args[0].class == String
|
|
113
|
+
from_string(args[0])
|
|
114
|
+
return
|
|
115
|
+
else
|
|
116
|
+
from_data(args[0])
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
# raise ArgumentError.new("Don't call new! Use Dnsruby::RR::create() instead")
|
|
122
|
+
end
|
|
123
|
+
public
|
|
124
|
+
|
|
125
|
+
def from_hash(hash) #:nodoc: all
|
|
126
|
+
hash.keys.each do |param|
|
|
127
|
+
send("#{param}=", hash[param])
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Create a new RR from the hash. The name is required; all other fields are optional.
|
|
132
|
+
# Type defaults to ANY and the Class defaults to IN. The TTL defaults to 0.
|
|
133
|
+
#
|
|
134
|
+
# If the type is specified, then it is necessary to provide ALL of the resource record fields which
|
|
135
|
+
# are specific to that record; i.e. for
|
|
136
|
+
# an MX record, you would need to specify the exchange and the preference
|
|
137
|
+
#
|
|
138
|
+
# require 'Dnsruby'
|
|
139
|
+
# rr = Dnsruby::RR.new_from_hash({:name => "example.com"})
|
|
140
|
+
# rr = Dnsruby::RR.new_from_hash({:name => "example.com", :type => Types.MX, :ttl => 10, :preference => 5, :exchange => "mx1.example.com"})
|
|
141
|
+
def RR.new_from_hash(inhash)
|
|
142
|
+
hash = inhash.clone
|
|
143
|
+
type = hash[:type] || Types::ANY
|
|
144
|
+
klass = hash[:klass] || Classes::IN
|
|
145
|
+
ttl = hash[:ttl] || 0
|
|
146
|
+
record_class = get_class(type, klass)
|
|
147
|
+
record = record_class.new
|
|
148
|
+
record.name = hash[:name]
|
|
149
|
+
unless record.name.kind_of?(Name)
|
|
150
|
+
record.name = Name.create(record.name)
|
|
151
|
+
end
|
|
152
|
+
record.ttl = ttl
|
|
153
|
+
record.type = type
|
|
154
|
+
record.klass = klass
|
|
155
|
+
hash.delete(:name)
|
|
156
|
+
hash.delete(:type)
|
|
157
|
+
hash.delete(:ttl)
|
|
158
|
+
hash.delete(:klass)
|
|
159
|
+
record.from_hash(hash)
|
|
160
|
+
record
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Returns a Dnsruby::RR object of the appropriate type and
|
|
164
|
+
# initialized from the string passed by the user. The format of the
|
|
165
|
+
# string is that used in zone files, and is compatible with the string
|
|
166
|
+
# returned by Net::DNS::RR.inspect
|
|
167
|
+
#
|
|
168
|
+
# The name and RR type are required; all other information is optional.
|
|
169
|
+
# If omitted, the TTL defaults to 0 and the RR class defaults to IN.
|
|
170
|
+
#
|
|
171
|
+
# All names must be fully qualified. The trailing dot (.) is optional.
|
|
172
|
+
#
|
|
173
|
+
#
|
|
174
|
+
# a = Dnsruby::RR.new_from_string("foo.example.com. 86400 A 10.1.2.3")
|
|
175
|
+
# mx = Dnsruby::RR.new_from_string("example.com. 7200 MX 10 mailhost.example.com.")
|
|
176
|
+
# cname = Dnsruby::RR.new_from_string("www.example.com 300 IN CNAME www1.example.com")
|
|
177
|
+
# txt = Dnsruby::RR.new_from_string('baz.example.com 3600 HS TXT "text record"')
|
|
178
|
+
#
|
|
179
|
+
#
|
|
180
|
+
def RR.new_from_string(rrstring)
|
|
181
|
+
# strip out comments
|
|
182
|
+
# Test for non escaped ";" by means of the look-behind assertion
|
|
183
|
+
# (the backslash is escaped)
|
|
184
|
+
rrstring = rrstring.gsub(/(\?<!\\);.*/o, '')
|
|
185
|
+
|
|
186
|
+
matches = (/#{@@RR_REGEX}/xo).match(rrstring)
|
|
187
|
+
unless matches
|
|
188
|
+
raise "#{rrstring} did not match RR pattern. Please report this to the author!"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
name = matches[1]
|
|
192
|
+
ttl = matches[2].to_i || 0
|
|
193
|
+
rrclass = matches[3] || ''
|
|
194
|
+
rrtype = matches[4] || ''
|
|
195
|
+
rdata = matches[5] || ''
|
|
196
|
+
|
|
197
|
+
rdata.gsub!(/\s+$/o, '') if rdata
|
|
198
|
+
|
|
199
|
+
# RFC3597 tweaks
|
|
200
|
+
# This converts to known class and type if specified as TYPE###
|
|
201
|
+
if rrtype =~/^TYPE\d+/o
|
|
202
|
+
rrtype = Dnsruby::Types.typesbyval(Dnsruby::Types::typesbyname(rrtype))
|
|
203
|
+
end
|
|
204
|
+
if rrclass =~/^CLASS\d+/o
|
|
205
|
+
rrclass = Dnsruby::Classes.classesbyval(Dnsruby::Classes::classesbyname(rrclass))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
if rrtype == '' && rrclass == 'ANY'
|
|
209
|
+
rrtype = 'ANY'
|
|
210
|
+
rrclass = 'IN'
|
|
211
|
+
elsif rrclass == ''
|
|
212
|
+
rrclass = 'IN'
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
if rrtype == ''
|
|
216
|
+
rrtype = 'ANY'
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
unless %w(NAPTR TXT).include?(rrtype)
|
|
220
|
+
if rdata
|
|
221
|
+
rdata.gsub!('(', '')
|
|
222
|
+
rdata.gsub!(')', '')
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
test_length = ->(hexdump, rdlength) do
|
|
227
|
+
if hexdump.length != rdlength * 2
|
|
228
|
+
raise "#{rdata} is inconsistent; length should be #{rdlength * 2} but is #{hexdump.length}."
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
pack_rdata = ->(regex) do
|
|
233
|
+
rdata =~ regex
|
|
234
|
+
matches = regex.match(rdata)
|
|
235
|
+
rdlength = matches[1].to_i
|
|
236
|
+
hexdump = matches[2].gsub(/\s*/, '')
|
|
237
|
+
|
|
238
|
+
test_length.(hexdump, rdlength)
|
|
239
|
+
packed_rdata = [hexdump].pack('H*')
|
|
240
|
+
|
|
241
|
+
[packed_rdata, rdlength]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
if implemented_rrs.include?(rrtype) && rdata !~/^\s*\\#/o
|
|
245
|
+
return _get_subclass(name, rrtype, rrclass, ttl, rdata)
|
|
246
|
+
elsif implemented_rrs.include?(rrtype) # A known RR type starting with \#
|
|
247
|
+
packed_rdata, rdlength = pack_rdata.(/\\#\s+(\d+)\s+(.*)$/o)
|
|
248
|
+
return new_from_data(name, rrtype, rrclass, ttl, rdlength, packed_rdata, 0) # rdata.length() - rdlength);
|
|
249
|
+
elsif rdata =~ /\s*\\#\s+\d+\s+/o
|
|
250
|
+
regex = /\\#\s+(\d+)\s+(.*)$/o
|
|
251
|
+
# We are now dealing with the truly unknown.
|
|
252
|
+
raise 'Expected RFC3597 representation of RDATA' unless rdata =~ regex
|
|
253
|
+
packed_rdata, rdlength = pack_rdata.(regex)
|
|
254
|
+
return new_from_data(name, rrtype, rrclass, ttl, rdlength, packed_rdata, 0) # rdata.length() - rdlength);
|
|
255
|
+
else
|
|
256
|
+
# God knows how to handle these...
|
|
257
|
+
return _get_subclass(name, rrtype, rrclass, ttl, '')
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def RR.new_from_data(*args) #:nodoc: all
|
|
262
|
+
name, rrtype, rrclass, ttl, rdlength, data, offset = args
|
|
263
|
+
rdata = data ? data[offset, rdlength] : []
|
|
264
|
+
decoder = MessageDecoder.new(rdata)
|
|
265
|
+
record = get_class(rrtype, rrclass).decode_rdata(decoder)
|
|
266
|
+
record.name = Name.create(name)
|
|
267
|
+
record.ttl = ttl
|
|
268
|
+
record.type = rrtype
|
|
269
|
+
record.klass = rrclass
|
|
270
|
+
record
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Return an array of all the currently implemented RR types
|
|
274
|
+
def RR.implemented_rrs
|
|
275
|
+
@@implemented_rr_map ||= ClassHash.keys.map { |key| Dnsruby::Types.to_string(key[0]) }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
class << self
|
|
279
|
+
private
|
|
280
|
+
def _get_subclass(name, rrtype, rrclass, ttl, rdata) #:nodoc: all
|
|
281
|
+
return unless (rrtype!=nil)
|
|
282
|
+
record = get_class(rrtype, rrclass).new(rdata)
|
|
283
|
+
record.name = Name.create(name)
|
|
284
|
+
record.ttl = ttl
|
|
285
|
+
record.type = rrtype
|
|
286
|
+
record.klass = rrclass
|
|
287
|
+
return record
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Returns a string representation of the RR in zone file format
|
|
292
|
+
def to_s
|
|
293
|
+
s = name ? (name.to_s(true) + "\t") : ''
|
|
294
|
+
s << [ttl, klass, type, rdata_to_string].map(&:to_s).join("\t")
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Get a string representation of the data section of the RR (in zone file format)
|
|
298
|
+
def rdata_to_string
|
|
299
|
+
(@rdata && @rdata.length > 0) ? @rdata : 'no rdata'
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def from_data(data) #:nodoc: all
|
|
303
|
+
# to be implemented by subclasses
|
|
304
|
+
raise NotImplementedError.new
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def from_string(input) #:nodoc: all
|
|
308
|
+
# to be implemented by subclasses
|
|
309
|
+
# raise NotImplementedError.new
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def encode_rdata(msg, canonical=false) #:nodoc: all
|
|
313
|
+
# to be implemented by subclasses
|
|
314
|
+
raise EncodeError.new("#{self.class} is RR.")
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def self.decode_rdata(msg) #:nodoc: all
|
|
318
|
+
# to be implemented by subclasses
|
|
319
|
+
raise DecodeError.new("#{self.class} is RR.")
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def ==(other)
|
|
323
|
+
|
|
324
|
+
return false unless self.class == other.class
|
|
325
|
+
|
|
326
|
+
ivars_to_compare = ->(object) do
|
|
327
|
+
ivars = object.instance_variables.map { |var| var.to_s }
|
|
328
|
+
ivars.delete '@ttl' # RFC 2136 section 1.1
|
|
329
|
+
ivars.delete '@rdata'
|
|
330
|
+
if self.type == Types.DS
|
|
331
|
+
ivars.delete '@digest'
|
|
332
|
+
end
|
|
333
|
+
ivars.sort
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
get_instance_var_values = ->(object, ivar_names) do
|
|
337
|
+
ivar_names.map { |ivar_name| object.instance_variable_get(ivar_name) }
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
self_ivars = ivars_to_compare.(self)
|
|
341
|
+
other_ivars = ivars_to_compare.(other)
|
|
342
|
+
return false unless self_ivars == other_ivars
|
|
343
|
+
|
|
344
|
+
self_values = get_instance_var_values.(self, self_ivars)
|
|
345
|
+
other_values = get_instance_var_values.(other, other_ivars)
|
|
346
|
+
self_values == other_values
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def eql?(other) #:nodoc:
|
|
350
|
+
self == other
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def hash # :nodoc:
|
|
354
|
+
vars = self.instance_variables - ['@ttl']
|
|
355
|
+
vars.inject(0) do |hash_value, var_name|
|
|
356
|
+
hash_value ^ self.instance_variable_get(var_name).hash
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def self.find_class(type_value, class_value) # :nodoc: all
|
|
361
|
+
if !! (ret = ClassHash[[type_value, class_value]])
|
|
362
|
+
return ret
|
|
363
|
+
elsif !! (val = ClassInsensitiveTypes[type_value])
|
|
364
|
+
klass = Class.new(val)
|
|
365
|
+
klass.const_set(:TypeValue, type_value)
|
|
366
|
+
klass.const_set(:ClassValue, class_value)
|
|
367
|
+
return klass
|
|
368
|
+
else
|
|
369
|
+
return Generic.create(type_value, class_value)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Get an RR of the specified type and class
|
|
374
|
+
def self.get_class(type_value, class_value) #:nodoc: all
|
|
375
|
+
if type_value == Types::OPT
|
|
376
|
+
return Class.new(OPT)
|
|
377
|
+
elsif type_value.class == Class
|
|
378
|
+
type_value = type_value.const_get(:TypeValue)
|
|
379
|
+
return find_class(type_value, Classes.to_code(class_value))
|
|
380
|
+
else
|
|
381
|
+
type_value = (type_value.class == Types) ? type_value.code : Types.new(type_value).code
|
|
382
|
+
class_value = (class_value.class == Classes) ? class_value.code : Classes.new(class_value).code
|
|
383
|
+
return find_class(type_value, class_value)
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# Create a new RR from the arguments, which can be either a String or a Hash.
|
|
389
|
+
# See new_from_string and new_from_hash for details
|
|
390
|
+
#
|
|
391
|
+
# a = Dnsruby::RR.create('foo.example.com. 86400 A 10.1.2.3')
|
|
392
|
+
# mx = Dnsruby::RR.create('example.com. 7200 MX 10 mailhost.example.com.')
|
|
393
|
+
# cname = Dnsruby::RR.create('www.example.com 300 IN CNAME www1.example.com')
|
|
394
|
+
# txt = Dnsruby::RR.create('baz.example.com 3600 HS TXT 'text record'')
|
|
395
|
+
#
|
|
396
|
+
# rr = Dnsruby::RR.create({:name => 'example.com'})
|
|
397
|
+
# rr = Dnsruby::RR.create({:name => 'example.com', :type => 'MX', :ttl => 10,
|
|
398
|
+
# :preference => 5, :exchange => 'mx1.example.com'})
|
|
399
|
+
#
|
|
400
|
+
def RR.create(*args)
|
|
401
|
+
case args[0]
|
|
402
|
+
when String
|
|
403
|
+
new_from_string(args[0])
|
|
404
|
+
when Hash
|
|
405
|
+
new_from_hash(args[0])
|
|
406
|
+
else
|
|
407
|
+
new_from_data(args)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def self.get_num(bytes)
|
|
412
|
+
ret = 0
|
|
413
|
+
shift = (bytes.length - 1) * 8
|
|
414
|
+
bytes.each_byte do |byte|
|
|
415
|
+
ret += byte.to_i << shift
|
|
416
|
+
shift -= 8
|
|
417
|
+
end
|
|
418
|
+
ret
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# --
|
|
2
|
+
# Copyright 2007 Nominet UK
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
# ++
|
|
16
|
+
module Dnsruby
|
|
17
|
+
class RR
|
|
18
|
+
# (RFC4034, section 3)
|
|
19
|
+
# DNSSEC uses public key cryptography to sign and authenticate DNS
|
|
20
|
+
# resource record sets (RRsets). Digital signatures are stored in
|
|
21
|
+
# RRSIG resource records and are used in the DNSSEC authentication
|
|
22
|
+
# process described in [RFC4035]. A validator can use these RRSIG RRs
|
|
23
|
+
# to authenticate RRsets from the zone. The RRSIG RR MUST only be used
|
|
24
|
+
# to carry verification material (digital signatures) used to secure
|
|
25
|
+
# DNS operations.
|
|
26
|
+
#
|
|
27
|
+
# An RRSIG record contains the signature for an RRset with a particular
|
|
28
|
+
# name, class, and type. The RRSIG RR specifies a validity interval
|
|
29
|
+
# for the signature and uses the Algorithm, the Signer's Name, and the
|
|
30
|
+
# Key Tag to identify the DNSKEY RR containing the public key that a
|
|
31
|
+
# validator can use to verify the signature.
|
|
32
|
+
class RRSIG < RR
|
|
33
|
+
ClassValue = nil #:nodoc: all
|
|
34
|
+
TypeValue = Types::RRSIG #:nodoc: all
|
|
35
|
+
|
|
36
|
+
# 3.1. RRSIG RDATA Wire Format
|
|
37
|
+
#
|
|
38
|
+
# The RDATA for an RRSIG RR consists of a 2 octet Type Covered field, a
|
|
39
|
+
# 1 octet Algorithm field, a 1 octet Labels field, a 4 octet Original
|
|
40
|
+
# TTL field, a 4 octet Signature Expiration field, a 4 octet Signature
|
|
41
|
+
# Inception field, a 2 octet Key tag, the Signer's Name field, and the
|
|
42
|
+
# Signature field.
|
|
43
|
+
#
|
|
44
|
+
# 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
|
|
45
|
+
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
46
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
47
|
+
# | Type Covered | Algorithm | Labels |
|
|
48
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
49
|
+
# | Original TTL |
|
|
50
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
51
|
+
# | Signature Expiration |
|
|
52
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
53
|
+
# | Signature Inception |
|
|
54
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
55
|
+
# | Key Tag | /
|
|
56
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Signer's Name /
|
|
57
|
+
# / /
|
|
58
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
59
|
+
# / /
|
|
60
|
+
# / Signature /
|
|
61
|
+
# / /
|
|
62
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
63
|
+
|
|
64
|
+
# The type covered by this RRSIG
|
|
65
|
+
attr_reader :type_covered
|
|
66
|
+
# The algorithm used for this RRSIG
|
|
67
|
+
# See Dnsruby::Algorithms for permitted values
|
|
68
|
+
attr_reader :algorithm
|
|
69
|
+
# The number of labels in the original RRSIG RR owner name
|
|
70
|
+
# Can be used to determine if name was synthesised from a wildcard.
|
|
71
|
+
attr_accessor :labels
|
|
72
|
+
# The TTL of the covered RRSet as it appears in the authoritative zone
|
|
73
|
+
attr_accessor :original_ttl
|
|
74
|
+
# The signature expiration
|
|
75
|
+
attr_accessor :expiration
|
|
76
|
+
# The signature inception
|
|
77
|
+
attr_accessor :inception
|
|
78
|
+
# The key tag value of the DNSKEY RR that validates this signature
|
|
79
|
+
attr_accessor :key_tag
|
|
80
|
+
# identifies the owner name of the DNSKEY RR that a validator is
|
|
81
|
+
# supposed to use to validate this signature
|
|
82
|
+
attr_reader :signers_name
|
|
83
|
+
|
|
84
|
+
# contains the cryptographic signature that covers
|
|
85
|
+
# the RRSIG RDATA (excluding the Signature field) and the RRset
|
|
86
|
+
# specified by the RRSIG owner name, RRSIG class, and RRSIG Type
|
|
87
|
+
# Covered field
|
|
88
|
+
attr_accessor :signature
|
|
89
|
+
|
|
90
|
+
def init_defaults
|
|
91
|
+
@algorithm=Algorithms.RSASHA1
|
|
92
|
+
@type_covered = Types::A
|
|
93
|
+
@original_ttl = 3600
|
|
94
|
+
@inception = Time.now.to_i
|
|
95
|
+
@expiration = Time.now.to_i
|
|
96
|
+
@key_tag = 0
|
|
97
|
+
@labels = 0
|
|
98
|
+
self.signers_name="."
|
|
99
|
+
@signature = "\0"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def algorithm=(a)
|
|
103
|
+
if (a.instance_of?String)
|
|
104
|
+
if (a.to_i > 0)
|
|
105
|
+
a = a.to_i
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
begin
|
|
109
|
+
alg = Algorithms.new(a)
|
|
110
|
+
@algorithm = alg
|
|
111
|
+
rescue ArgumentError => e
|
|
112
|
+
raise DecodeError.new(e)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def type_covered=(t)
|
|
117
|
+
begin
|
|
118
|
+
type = Types.new(t)
|
|
119
|
+
@type_covered = type
|
|
120
|
+
rescue ArgumentError => e
|
|
121
|
+
raise DecodeError.new(e)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def signers_name=(s)
|
|
126
|
+
begin
|
|
127
|
+
name = Name.create(s)
|
|
128
|
+
@signers_name = name
|
|
129
|
+
rescue ArgumentError => e
|
|
130
|
+
raise DecodeError.new(e)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def from_data(data) #:nodoc: all
|
|
136
|
+
type_covered, algorithm, @labels, @original_ttl, expiration, inception,
|
|
137
|
+
@key_tag, signers_name, @signature = data
|
|
138
|
+
@expiration = expiration
|
|
139
|
+
@inception = inception
|
|
140
|
+
self.type_covered=(type_covered)
|
|
141
|
+
self.signers_name=(signers_name)
|
|
142
|
+
self.algorithm=(algorithm)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def from_string(input)
|
|
146
|
+
if (input.length > 0)
|
|
147
|
+
data = input.split(" ")
|
|
148
|
+
self.type_covered=(data[0])
|
|
149
|
+
self.algorithm=(data[1])
|
|
150
|
+
self.labels=data[2].to_i
|
|
151
|
+
self.original_ttl=data[3].to_i
|
|
152
|
+
self.expiration=get_time(data[4])
|
|
153
|
+
# Brackets may also be present
|
|
154
|
+
index = 5
|
|
155
|
+
end_index = data.length - 1
|
|
156
|
+
if (data[index]=="(")
|
|
157
|
+
index = 6
|
|
158
|
+
end_index = data.length - 2
|
|
159
|
+
end
|
|
160
|
+
self.inception=get_time(data[index])
|
|
161
|
+
self.key_tag=data[index+1].to_i
|
|
162
|
+
self.signers_name=(data[index+2])
|
|
163
|
+
# signature can include whitespace - include all text
|
|
164
|
+
# until we come to " )" at the end, and then gsub
|
|
165
|
+
# the white space out
|
|
166
|
+
buf=""
|
|
167
|
+
(index+3..end_index).each {|i|
|
|
168
|
+
if (comment_index = data[i].index(";"))
|
|
169
|
+
buf += data[i].slice(0, comment_index)
|
|
170
|
+
# @TODO@ We lose the comments here - we should really keep them for when we write back to string format?
|
|
171
|
+
break
|
|
172
|
+
else
|
|
173
|
+
buf += data[i]
|
|
174
|
+
end
|
|
175
|
+
}
|
|
176
|
+
buf.gsub!(/\n/, "")
|
|
177
|
+
buf.gsub!(/ /, "")
|
|
178
|
+
# self.signature=Base64.decode64(buf)
|
|
179
|
+
self.signature=buf.unpack("m*")[0]
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def RRSIG.get_time(input)
|
|
184
|
+
if (input.kind_of?Fixnum)
|
|
185
|
+
return input
|
|
186
|
+
end
|
|
187
|
+
# RFC 4034, section 3.2
|
|
188
|
+
# The Signature Expiration Time and Inception Time field values MUST be
|
|
189
|
+
# represented either as an unsigned decimal integer indicating seconds
|
|
190
|
+
# since 1 January 1970 00:00:00 UTC, or in the form YYYYMMDDHHmmSS in
|
|
191
|
+
# UTC, where:
|
|
192
|
+
#
|
|
193
|
+
# YYYY is the year (0001-9999, but see Section 3.1.5);
|
|
194
|
+
# MM is the month number (01-12);
|
|
195
|
+
# DD is the day of the month (01-31);
|
|
196
|
+
# HH is the hour, in 24 hour notation (00-23);
|
|
197
|
+
# mm is the minute (00-59); and
|
|
198
|
+
# SS is the second (00-59).
|
|
199
|
+
#
|
|
200
|
+
# Note that it is always possible to distinguish between these two
|
|
201
|
+
# formats because the YYYYMMDDHHmmSS format will always be exactly 14
|
|
202
|
+
# digits, while the decimal representation of a 32-bit unsigned integer
|
|
203
|
+
# can never be longer than 10 digits.
|
|
204
|
+
if (input.length == 10)
|
|
205
|
+
return input.to_i
|
|
206
|
+
elsif (input.length == 14)
|
|
207
|
+
year = input[0,4]
|
|
208
|
+
mon=input[4,2]
|
|
209
|
+
day=input[6,2]
|
|
210
|
+
hour=input[8,2]
|
|
211
|
+
min=input[10,2]
|
|
212
|
+
sec=input[12,2]
|
|
213
|
+
# @TODO@ REPLACE THIS BY LOCAL CODE - Time.gm DOG SLOW!
|
|
214
|
+
return Time.gm(year, mon, day, hour, min, sec).to_i
|
|
215
|
+
else
|
|
216
|
+
raise DecodeError.new("RRSIG : Illegal time value #{input} - see RFC 4034 section 3.2")
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def get_time(input)
|
|
221
|
+
return RRSIG.get_time(input)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def format_time(time)
|
|
225
|
+
return Time.at(time).gmtime.strftime("%Y%m%d%H%M%S")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def rdata_to_string #:nodoc: all
|
|
229
|
+
if (@type_covered!=nil)
|
|
230
|
+
# signature = Base64.encode64(@signature) # .gsub(/\n/, "")
|
|
231
|
+
signature = [@signature].pack("m*").gsub(/\n/, "")
|
|
232
|
+
# @TODO@ Display the expiration and inception as
|
|
233
|
+
return "#{@type_covered.string} #{@algorithm.string} #{@labels} #{@original_ttl} " +
|
|
234
|
+
"#{format_time(@expiration)} ( #{format_time(@inception)} " +
|
|
235
|
+
"#{@key_tag} #{@signers_name.to_s(true)} #{signature} )"
|
|
236
|
+
else
|
|
237
|
+
return ""
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def encode_rdata(msg, canonical=false) #:nodoc: all
|
|
242
|
+
# 2 octets, then 2 sets of 1 octet
|
|
243
|
+
msg.put_pack('ncc', @type_covered.to_i, @algorithm.to_i, @labels)
|
|
244
|
+
msg.put_pack("NNN", @original_ttl, @expiration, @inception)
|
|
245
|
+
msg.put_pack("n", @key_tag)
|
|
246
|
+
msg.put_name(@signers_name, canonical, false)
|
|
247
|
+
msg.put_bytes(@signature)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def self.decode_rdata(msg) #:nodoc: all
|
|
251
|
+
type_covered, algorithm, labels = msg.get_unpack('ncc')
|
|
252
|
+
original_ttl, expiration, inception = msg.get_unpack('NNN')
|
|
253
|
+
key_tag, = msg.get_unpack('n')
|
|
254
|
+
signers_name = msg.get_name
|
|
255
|
+
signature = msg.get_bytes
|
|
256
|
+
return self.new(
|
|
257
|
+
[type_covered, algorithm, labels, original_ttl, expiration,
|
|
258
|
+
inception, key_tag, signers_name, signature])
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def sig_data
|
|
262
|
+
# RRSIG_RDATA is the wire format of the RRSIG RDATA fields
|
|
263
|
+
# with the Signer's Name field in canonical form and
|
|
264
|
+
# the Signature field excluded;
|
|
265
|
+
data = MessageEncoder.new { |msg|
|
|
266
|
+
msg.put_pack('ncc', @type_covered.to_i, @algorithm.to_i, @labels)
|
|
267
|
+
msg.put_pack("NNN", @original_ttl, @expiration, @inception)
|
|
268
|
+
msg.put_pack("n", @key_tag)
|
|
269
|
+
msg.put_name(@signers_name, true)
|
|
270
|
+
}.to_s
|
|
271
|
+
return data
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|