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.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -0
  3. data/Rakefile +30 -29
  4. data/demo/axfr.rb +93 -93
  5. data/demo/check_soa.rb +99 -99
  6. data/demo/check_zone.rb +59 -59
  7. data/demo/digdlv.rb +43 -43
  8. data/demo/digroot.rb +34 -34
  9. data/demo/example_recurse.rb +14 -14
  10. data/demo/mresolv.rb +30 -30
  11. data/demo/mx.rb +31 -31
  12. data/demo/rubydig.rb +37 -37
  13. data/demo/to_resolve.txt +3088 -3088
  14. data/demo/trace_dns.rb +46 -46
  15. data/lib/dnsruby.rb +161 -526
  16. data/lib/dnsruby/DNS.rb +305 -0
  17. data/lib/{Dnsruby/Cache.rb → dnsruby/cache.rb} +152 -152
  18. data/lib/{Dnsruby → dnsruby}/code_mapper.rb +48 -52
  19. data/lib/dnsruby/code_mappers.rb +295 -0
  20. data/lib/{Dnsruby/Config.rb → dnsruby/config.rb} +454 -454
  21. data/lib/{Dnsruby → dnsruby}/dnssec.rb +91 -91
  22. data/lib/{Dnsruby/Hosts.rb → dnsruby/hosts.rb} +125 -125
  23. data/lib/{Dnsruby → dnsruby}/ipv4.rb +26 -26
  24. data/lib/{Dnsruby → dnsruby}/ipv6.rb +42 -42
  25. data/lib/{Dnsruby → dnsruby}/key_cache.rb +29 -29
  26. data/lib/dnsruby/message/decoder.rb +164 -0
  27. data/lib/dnsruby/message/encoder.rb +75 -0
  28. data/lib/dnsruby/message/header.rb +249 -0
  29. data/lib/dnsruby/message/message.rb +629 -0
  30. data/lib/dnsruby/message/question.rb +86 -0
  31. data/lib/dnsruby/message/section.rb +96 -0
  32. data/lib/{Dnsruby → dnsruby}/name.rb +141 -141
  33. data/lib/dnsruby/packet_sender.rb +661 -0
  34. data/lib/{Dnsruby/Recursor.rb → dnsruby/recursor.rb} +235 -233
  35. data/lib/dnsruby/resolv.rb +113 -0
  36. data/lib/dnsruby/resolver.rb +1192 -0
  37. data/lib/dnsruby/resource/A.rb +56 -0
  38. data/lib/dnsruby/resource/AAAA.rb +54 -0
  39. data/lib/{Dnsruby → dnsruby}/resource/AFSDB.rb +68 -68
  40. data/lib/{Dnsruby → dnsruby}/resource/CERT.rb +105 -105
  41. data/lib/{Dnsruby → dnsruby}/resource/DHCID.rb +54 -54
  42. data/lib/dnsruby/resource/DLV.rb +27 -0
  43. data/lib/{Dnsruby → dnsruby}/resource/DNSKEY.rb +372 -372
  44. data/lib/{Dnsruby → dnsruby}/resource/DS.rb +255 -255
  45. data/lib/{Dnsruby → dnsruby}/resource/HINFO.rb +71 -71
  46. data/lib/{Dnsruby → dnsruby}/resource/HIP.rb +29 -29
  47. data/lib/{Dnsruby → dnsruby}/resource/IN.rb +30 -30
  48. data/lib/{Dnsruby → dnsruby}/resource/IPSECKEY.rb +31 -31
  49. data/lib/{Dnsruby → dnsruby}/resource/ISDN.rb +62 -62
  50. data/lib/{Dnsruby → dnsruby}/resource/KX.rb +65 -65
  51. data/lib/{Dnsruby → dnsruby}/resource/LOC.rb +263 -263
  52. data/lib/{Dnsruby → dnsruby}/resource/MINFO.rb +69 -69
  53. data/lib/{Dnsruby → dnsruby}/resource/MX.rb +65 -65
  54. data/lib/{Dnsruby → dnsruby}/resource/NAPTR.rb +98 -98
  55. data/lib/{Dnsruby → dnsruby}/resource/NSAP.rb +171 -171
  56. data/lib/dnsruby/resource/NSEC.rb +275 -0
  57. data/lib/dnsruby/resource/NSEC3.rb +332 -0
  58. data/lib/dnsruby/resource/NSEC3PARAM.rb +135 -0
  59. data/lib/dnsruby/resource/OPT.rb +272 -0
  60. data/lib/{Dnsruby → dnsruby}/resource/PX.rb +70 -70
  61. data/lib/{Dnsruby → dnsruby}/resource/RP.rb +75 -75
  62. data/lib/dnsruby/resource/RR.rb +421 -0
  63. data/lib/dnsruby/resource/RRSIG.rb +275 -0
  64. data/lib/dnsruby/resource/RRSet.rb +190 -0
  65. data/lib/{Dnsruby → dnsruby}/resource/RT.rb +67 -67
  66. data/lib/{Dnsruby → dnsruby}/resource/SOA.rb +94 -94
  67. data/lib/dnsruby/resource/SPF.rb +29 -0
  68. data/lib/dnsruby/resource/SRV.rb +112 -0
  69. data/lib/{Dnsruby → dnsruby}/resource/SSHFP.rb +14 -14
  70. data/lib/dnsruby/resource/TKEY.rb +163 -0
  71. data/lib/dnsruby/resource/TSIG.rb +593 -0
  72. data/lib/{Dnsruby → dnsruby}/resource/TXT.rb +191 -191
  73. data/lib/dnsruby/resource/X25.rb +55 -0
  74. data/lib/{Dnsruby → dnsruby}/resource/domain_name.rb +25 -25
  75. data/lib/{Dnsruby → dnsruby}/resource/generic.rb +80 -80
  76. data/lib/dnsruby/resource/resource.rb +25 -0
  77. data/lib/{Dnsruby → dnsruby}/select_thread.rb +148 -148
  78. data/lib/{Dnsruby/SingleResolver.rb → dnsruby/single_resolver.rb} +60 -60
  79. data/lib/{Dnsruby → dnsruby}/single_verifier.rb +344 -344
  80. data/lib/dnsruby/the_log.rb +44 -0
  81. data/lib/dnsruby/update.rb +278 -0
  82. data/lib/dnsruby/validator_thread.rb +124 -0
  83. data/lib/dnsruby/version.rb +3 -0
  84. data/lib/{Dnsruby → dnsruby}/zone_reader.rb +93 -93
  85. data/lib/{Dnsruby → dnsruby}/zone_transfer.rb +377 -377
  86. data/test/spec_helper.rb +16 -0
  87. data/test/tc_axfr.rb +31 -34
  88. data/test/tc_cache.rb +32 -32
  89. data/test/tc_dlv.rb +28 -28
  90. data/test/tc_dns.rb +73 -76
  91. data/test/tc_dnskey.rb +31 -32
  92. data/test/tc_dnsruby.rb +50 -44
  93. data/test/tc_ds.rb +36 -36
  94. data/test/tc_escapedchars.rb +252 -255
  95. data/test/tc_hash.rb +17 -21
  96. data/test/tc_header.rb +48 -57
  97. data/test/tc_hip.rb +19 -22
  98. data/test/tc_ipseckey.rb +18 -21
  99. data/test/tc_keith.rb +300 -0
  100. data/test/tc_message.rb +87 -0
  101. data/test/tc_misc.rb +83 -87
  102. data/test/tc_name.rb +81 -84
  103. data/test/tc_naptr.rb +18 -21
  104. data/test/tc_nsec.rb +55 -55
  105. data/test/tc_nsec3.rb +23 -24
  106. data/test/tc_nsec3param.rb +20 -21
  107. data/test/tc_packet.rb +90 -93
  108. data/test/tc_packet_unique_push.rb +48 -51
  109. data/test/tc_question.rb +30 -33
  110. data/test/tc_queue.rb +16 -17
  111. data/test/tc_recur.rb +16 -17
  112. data/test/tc_res_config.rb +38 -41
  113. data/test/tc_res_env.rb +29 -32
  114. data/test/tc_res_file.rb +26 -29
  115. data/test/tc_res_opt.rb +62 -65
  116. data/test/tc_resolver.rb +287 -242
  117. data/test/tc_rr-opt.rb +70 -63
  118. data/test/tc_rr-txt.rb +68 -71
  119. data/test/tc_rr-unknown.rb +45 -48
  120. data/test/tc_rr.rb +76 -70
  121. data/test/tc_rrset.rb +21 -22
  122. data/test/tc_rrsig.rb +19 -20
  123. data/test/tc_single_resolver.rb +294 -297
  124. data/test/tc_soak.rb +199 -202
  125. data/test/tc_soak_base.rb +29 -34
  126. data/test/tc_sshfp.rb +20 -23
  127. data/test/tc_tcp.rb +32 -35
  128. data/test/tc_tkey.rb +41 -44
  129. data/test/tc_tsig.rb +81 -84
  130. data/test/tc_update.rb +108 -111
  131. data/test/tc_validator.rb +29 -29
  132. data/test/tc_verifier.rb +81 -82
  133. data/test/ts_dnsruby.rb +16 -15
  134. data/test/ts_offline.rb +62 -63
  135. data/test/ts_online.rb +115 -115
  136. metadata +155 -90
  137. data/README +0 -59
  138. data/lib/Dnsruby/DNS.rb +0 -305
  139. data/lib/Dnsruby/PacketSender.rb +0 -656
  140. data/lib/Dnsruby/Resolver.rb +0 -1189
  141. data/lib/Dnsruby/TheLog.rb +0 -44
  142. data/lib/Dnsruby/message.rb +0 -1230
  143. data/lib/Dnsruby/resource/A.rb +0 -56
  144. data/lib/Dnsruby/resource/AAAA.rb +0 -54
  145. data/lib/Dnsruby/resource/DLV.rb +0 -27
  146. data/lib/Dnsruby/resource/NSEC.rb +0 -298
  147. data/lib/Dnsruby/resource/NSEC3.rb +0 -340
  148. data/lib/Dnsruby/resource/NSEC3PARAM.rb +0 -135
  149. data/lib/Dnsruby/resource/OPT.rb +0 -213
  150. data/lib/Dnsruby/resource/RRSIG.rb +0 -275
  151. data/lib/Dnsruby/resource/SPF.rb +0 -29
  152. data/lib/Dnsruby/resource/SRV.rb +0 -112
  153. data/lib/Dnsruby/resource/TKEY.rb +0 -163
  154. data/lib/Dnsruby/resource/TSIG.rb +0 -593
  155. data/lib/Dnsruby/resource/X25.rb +0 -55
  156. data/lib/Dnsruby/resource/resource.rb +0 -678
  157. data/lib/Dnsruby/update.rb +0 -278
  158. data/lib/Dnsruby/validator_thread.rb +0 -124
@@ -0,0 +1,29 @@
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
+ # DNS SPF resource record
19
+
20
+ # This is a clone of the TXT record. This class therfore completely inherits
21
+ # all properties of the Dnsruby::Resource::TXT class.
22
+ #
23
+ # Please see the Dnsruby::Resource::TXT documentation for details
24
+ # RFC 1035 Section 3.3.14, draft-schlitt-ospf-classic-02.txt
25
+ class SPF < TXT
26
+ TypeValue = Types::SPF #:nodoc: all
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,112 @@
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
+ module IN
19
+ # SRV resource record defined in RFC 2782
20
+ #
21
+ # These records identify the hostname and port that a service is
22
+ # available at.
23
+ #
24
+ # The format is:
25
+ # _Service._Proto.Name TTL Class SRV Priority Weight Port Target
26
+ #
27
+ # The fields specific to SRV are defined in RFC 2782
28
+ class SRV < RR
29
+ ClassHash[[TypeValue = Types::SRV, ClassValue = ClassValue]] = self #:nodoc: all
30
+
31
+ # The priority of this target host.
32
+ # A client MUST attempt
33
+ # to contact the target host with the lowest-numbered priority it can
34
+ # reach; target hosts with the same priority SHOULD be tried in an
35
+ # order defined by the weight field. The range is 0-65535. Note that
36
+ # it is not widely implemented and should be set to zero.
37
+ attr_accessor :priority
38
+
39
+ # A server selection mechanism.
40
+ # The weight field specifies
41
+ # a relative weight for entries with the same priority. Larger weights
42
+ # SHOULD be given a proportionately higher probability of being
43
+ # selected. The range of this number is 0-65535. Domain administrators
44
+ # SHOULD use Weight 0 when there isn't any server selection to do, to
45
+ # make the RR easier to read for humans (less noisy). Note that it is
46
+ # not widely implemented and should be set to zero.
47
+ attr_accessor :weight
48
+
49
+ # The port on this target host of this service. The range is 0-65535.
50
+ attr_accessor :port
51
+
52
+ # The domain name of the target host. A target of "." means
53
+ # that the service is decidedly not available at this domain.
54
+ attr_accessor :target
55
+
56
+ def from_data(data) #:nodoc: all
57
+ @priority, @weight, @port, @target = data
58
+ end
59
+
60
+ def from_hash(hash)
61
+ if hash[:priority]
62
+ @priority = hash[:priority].to_i
63
+ end
64
+ if hash[:weight]
65
+ @weight = hash[:weight].to_i
66
+ end
67
+ if hash[:port]
68
+ @port = hash[:port].to_i
69
+ end
70
+ if hash[:target]
71
+ @target= Name.create(hash[:target])
72
+ end
73
+ end
74
+
75
+ def from_string(input)
76
+ if (input.length > 0)
77
+ names = input.split(" ")
78
+ @priority = names[0].to_i
79
+ @weight = names[1].to_i
80
+ @port = names[2].to_i
81
+ if (names[3])
82
+ @target = Name.create(names[3])
83
+ end
84
+ end
85
+ end
86
+
87
+ def rdata_to_string
88
+ if (@target!=nil)
89
+ return "#{@priority} #{@weight} #{@port} #{@target.to_s(true)}"
90
+ else
91
+ return ""
92
+ end
93
+ end
94
+
95
+ def encode_rdata(msg, canonical=false) #:nodoc: all
96
+ msg.put_pack("n", @priority)
97
+ msg.put_pack("n", @weight)
98
+ msg.put_pack("n", @port)
99
+ msg.put_name(@target,canonical)
100
+ end
101
+
102
+ def self.decode_rdata(msg) #:nodoc: all
103
+ priority, = msg.get_unpack("n")
104
+ weight, = msg.get_unpack("n")
105
+ port, = msg.get_unpack("n")
106
+ target = msg.get_name
107
+ return self.new([priority, weight, port, target])
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,18 +1,18 @@
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
- #
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
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
- #++
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
16
  module Dnsruby
17
17
  class RR
18
18
  class SSHFP < RR
@@ -0,0 +1,163 @@
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
+
18
+ class Modes < CodeMapper
19
+ # The key is assigned by the server (unimplemented)
20
+ SERVERASSIGNED = 1
21
+
22
+ # The key is computed using a Diffie-Hellman key exchange
23
+ DIFFIEHELLMAN = 2
24
+
25
+ # The key is computed using GSS_API (unimplemented)
26
+ GSSAPI = 3
27
+
28
+ # The key is assigned by the resolver (unimplemented)
29
+ RESOLVERASSIGNED = 4
30
+
31
+ # The key should be deleted
32
+ DELETE = 5
33
+ update()
34
+ end
35
+
36
+ class RR
37
+ # RFC2930
38
+ class TKEY < RR
39
+ TypeValue = Types::TKEY #:nodoc: all
40
+ ClassValue = nil #:nodoc: all
41
+ ClassHash[[TypeValue, Classes::ANY]] = self #:nodoc: all
42
+
43
+ attr_reader :key_size
44
+ attr_accessor :key
45
+ # Gets or sets the domain name that specifies the name of the algorithm.
46
+ # The default algorithm is gss.microsoft.com
47
+ #
48
+ # rr.algorithm=(algorithm_name)
49
+ # print "algorithm = ", rr.algorithm, "\n"
50
+ #
51
+ attr_accessor :algorithm
52
+ # Gets or sets the inception time as the number of seconds since 1 Jan 1970
53
+ # 00:00:00 UTC.
54
+ #
55
+ # The default inception time is the current time.
56
+ #
57
+ # rr.inception=(time)
58
+ # print "inception = ", rr.inception, "\n"
59
+ #
60
+ attr_accessor :inception
61
+ # Gets or sets the expiration time as the number of seconds since 1 Jan 1970
62
+ # 00:00:00 UTC.
63
+ #
64
+ # The default expiration time is the current time plus 1 day.
65
+ #
66
+ # rr.expiration=(time)
67
+ # print "expiration = ", rr.expiration, "\n"
68
+ #
69
+ attr_accessor :expiration
70
+ # Sets the key mode (see rfc2930). The default is 3 which corresponds to GSSAPI
71
+ #
72
+ # rr.mode=(3)
73
+ # print "mode = ", rr.mode, "\n"
74
+ #
75
+ attr_accessor :mode
76
+ # Returns the RCODE covering TKEY processing. See RFC 2930 for details.
77
+ #
78
+ # print "error = ", rr.error, "\n"
79
+ #
80
+ attr_accessor :error
81
+ # Returns the length of the Other Data. Should be zero.
82
+ #
83
+ # print "other size = ", rr.other_size, "\n"
84
+ #
85
+ attr_reader :other_size
86
+ # Returns the Other Data. This field should be empty.
87
+ #
88
+ # print "other data = ", rr.other_data, "\n"
89
+ #
90
+ attr_reader :other_data
91
+
92
+ def other_data=(od)
93
+ @other_data=od
94
+ @other_size=@other_data.length
95
+ end
96
+
97
+ def initialize
98
+ @algorithm = "gss.microsoft.com"
99
+ @inception = Time.now
100
+ @expiration = Time.now + 24*60*60
101
+ @mode = Modes.GSSAPI
102
+ @error = 0
103
+ @other_size = 0
104
+ @other_data = ""
105
+
106
+ # RFC 2845 Section 2.3
107
+ @klass = Classes.ANY
108
+ # RFC 2845 Section 2.3
109
+ @ttl = 0
110
+ end
111
+
112
+ def from_hash(hash)
113
+ super(hash)
114
+ if (algorithm)
115
+ @algorithm = Name.create(hash[:algorithm])
116
+ end
117
+ end
118
+
119
+ def from_data(data) #:nodoc: all
120
+ @algorithm, @inception, @expiration, @mode, @error, @key_size, @key, @other_size, @other_data = data
121
+ end
122
+
123
+ # Create the RR from a standard string
124
+ def from_string(string) #:nodoc: all
125
+ Dnsruby.log.error("Dnsruby::RR::TKEY#from_string called, but no text format defined for TKEY")
126
+ end
127
+
128
+ def rdata_to_string
129
+ rdatastr=""
130
+
131
+ if (@algorithm!=nil)
132
+ error = @error
133
+ error = "UNDEFINED" unless error!=nil
134
+ rdatastr = "#{@algorithm.to_s(true)} #{error}"
135
+ if (@other_size != nil && @other_size >0 && @other_data!=nil)
136
+ rdatastr += " #{@other_data}"
137
+ end
138
+ end
139
+
140
+ return rdatastr
141
+ end
142
+
143
+ def encode_rdata(msg, canonical=false) #:nodoc: all
144
+ msg.put_name(@algorithm, canonical)
145
+ msg.put_pack("NNnn", @inception, @expiration, @mode, @error)
146
+ msg.put_pack("n", @key.length)
147
+ msg.put_bytes(@key)
148
+ msg.put_pack("n", @other_data.length)
149
+ msg.put_bytes(@other_data)
150
+ end
151
+
152
+ def self.decode_rdata(msg) #:nodoc: all
153
+ alg=msg.get_name
154
+ inc, exp, mode, error = msg.get_unpack("NNnn")
155
+ key_size, =msg.get_unpack("n")
156
+ key=msg.get_bytes(key_size)
157
+ other_size, =msg.get_unpack("n")
158
+ other=msg.get_bytes(other_size)
159
+ return self.new([alg, inc, exp, mode, error, key_size, key, other_size, other])
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,593 @@
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
+ # require 'base64'
17
+ begin
18
+ require 'openssl'
19
+ rescue LoadError
20
+ print "OpenSSL not found - ignoring\n"
21
+ end
22
+ module Dnsruby
23
+ class RR
24
+ # TSIG implements RFC2845.
25
+ #
26
+ # "This protocol allows for transaction level authentication using
27
+ # shared secrets and one way hashing. It can be used to authenticate
28
+ # dynamic updates as coming from an approved client, or to authenticate
29
+ # responses as coming from an approved recursive name server."
30
+ #
31
+ # A Dnsruby::RR::TSIG can represent the data present in a TSIG RR.
32
+ # However, it can also represent the data (specified in RFC2845) used
33
+ # to sign or verify a DNS message.
34
+ #
35
+ #
36
+ # Example code :
37
+ # res = Dnsruby::Resolver.new("ns0.validation-test-servers.nominet.org.uk")
38
+ #
39
+ # # Now configure the resolver with the TSIG key for signing/verifying
40
+ # KEY_NAME="rubytsig"
41
+ # KEY = "8n6gugn4aJ7MazyNlMccGKH1WxD2B3UvN/O/RA6iBupO2/03u9CTa3Ewz3gBWTSBCH3crY4Kk+tigNdeJBAvrw=="
42
+ # res.tsig=KEY_NAME, KEY
43
+ #
44
+ # update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk")
45
+ # # Generate update record name, and test it has been made. Then delete it and check it has been deleted
46
+ # update_name = generate_update_name
47
+ # update.absent(update_name)
48
+ # update.add(update_name, 'TXT', 100, "test signed update")
49
+ #
50
+ # # Resolver will automatically sign message and verify response
51
+ # response = res.send_message(update)
52
+ # assert(response.verified?) # Check that the response has been verified
53
+ class TSIG < RR
54
+ HMAC_MD5 = Name.create("HMAC-MD5.SIG-ALG.REG.INT.")
55
+ HMAC_SHA1 = Name.create("hmac-sha1.")
56
+ HMAC_SHA256 = Name.create("hmac-sha256.")
57
+
58
+ DEFAULT_FUDGE = 300
59
+
60
+ DEFAULT_ALGORITHM = HMAC_MD5
61
+
62
+ # Generates a TSIG record and adds it to the message.
63
+ # Takes an optional original_request argument for the case where this is
64
+ # a response to a query (RFC2845 3.4.1)
65
+ #
66
+ # Message#tsigstate will be set to :Signed.
67
+ def apply(message, original_request=nil)
68
+ if (!message.signed?)
69
+ tsig_rr = generate(message, original_request)
70
+ message.add_additional(tsig_rr)
71
+ message.tsigstate = :Signed
72
+ @query = message
73
+ tsig_rr.query = message
74
+ end
75
+ end
76
+
77
+ def query=q#:nodoc: all
78
+ @query = q
79
+ end
80
+
81
+
82
+ # Generates a TSIG record
83
+ def generate(msg, original_request = nil, data="", msg_bytes=nil, tsig_rr=self)#:nodoc: all
84
+ time_signed=@time_signed
85
+ if (!time_signed)
86
+ time_signed=Time.now.to_i
87
+ end
88
+ if (tsig_rr.time_signed)
89
+ time_signed = tsig_rr.time_signed
90
+ end
91
+
92
+ if (original_request)
93
+ # # Add the request MAC if present (used to validate responses).
94
+ # hmac.update(pack("H*", request_mac))
95
+ mac_bytes = MessageEncoder.new {|m|
96
+ m.put_pack('n', original_request.tsig.mac_size)
97
+ m.put_bytes(original_request.tsig.mac)
98
+ }.to_s
99
+ data += mac_bytes
100
+ # Original ID - should we set message ID to original ID?
101
+ if (tsig_rr != self)
102
+ msg.header.id = tsig_rr.original_id
103
+ else
104
+ msg.header.id = original_request.header.id
105
+ end
106
+ end
107
+
108
+ if (!msg_bytes)
109
+ msg_bytes = msg.encode
110
+ data += msg_bytes
111
+ else
112
+ # If msg_bytes came in, we need somehow to remove the TSIG RR
113
+ # It is the last record, so we can strip it if we know where it starts
114
+ # We must also poke the header ARcount to decrement it
115
+ msg_bytes = Header.decrement_arcount_encoded(msg_bytes)
116
+ data += msg_bytes[0, msg.tsigstart]
117
+ end
118
+
119
+ data += sig_data(tsig_rr, time_signed)
120
+
121
+ mac = calculate_mac(tsig_rr.algorithm, data)
122
+
123
+ mac_size = mac.length
124
+
125
+ new_tsig_rr = Dnsruby::RR.create({
126
+ :name => tsig_rr.name,
127
+ :type => Types.TSIG,
128
+ :ttl => tsig_rr.ttl,
129
+ :klass => tsig_rr.klass,
130
+ :algorithm => tsig_rr.algorithm,
131
+ :fudge => tsig_rr.fudge,
132
+ :key => @key,
133
+ :mac => mac,
134
+ :mac_size => mac_size,
135
+ :error => tsig_rr.error,
136
+ :time_signed => time_signed,
137
+ :original_id => msg.header.id
138
+ })
139
+ return new_tsig_rr
140
+
141
+ end
142
+
143
+ def calculate_mac(algorithm, data)
144
+ mac=nil
145
+ # + if (key_size > max_digest_len) {
146
+ # + EVP_DigestInit(&ectx, digester);
147
+ # + EVP_DigestUpdate(&ectx, (const void*) key_bytes, key_size);
148
+ # + EVP_DigestFinal(&ectx, key_bytes, NULL);
149
+ # + key_size = max_digest_len;
150
+ # + }
151
+ key = @key.gsub(" ", "")
152
+ # key = Base64::decode64(key)
153
+ key = key.unpack("m*")[0]
154
+ if (algorithm.to_s.downcase == HMAC_MD5.to_s.downcase)
155
+ mac = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, data)
156
+ elsif (algorithm == HMAC_SHA1)
157
+ mac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, key, data)
158
+ elsif (algorithm == HMAC_SHA256)
159
+ mac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, data)
160
+ else
161
+ # Should we allow client to pass in their own signing function?
162
+ raise VerifyError.new("Algorithm #{algorithm} unsupported by TSIG")
163
+ end
164
+ return mac
165
+ end
166
+
167
+ # Private method to return the TSIG RR data to be signed
168
+ def sig_data(tsig_rr, time_signed=@time_signed) #:nodoc: all
169
+ return MessageEncoder.new { |msg|
170
+ msg.put_name(tsig_rr.name.downcase, true)
171
+ msg.put_pack('nN', tsig_rr.klass.code, tsig_rr.ttl)
172
+ msg.put_name(tsig_rr.algorithm.downcase, true)
173
+
174
+ time_high = (time_signed >> 32)
175
+ time_low = (time_signed & 0xFFFFFFFF)
176
+ msg.put_pack('nN', time_high, time_low)
177
+ msg.put_pack('n', tsig_rr.fudge)
178
+ msg.put_pack('n', tsig_rr.error)
179
+ msg.put_pack('n', tsig_rr.other_size)
180
+ msg.put_bytes(tsig_rr.other_data)
181
+ }.to_s
182
+ end
183
+
184
+ # Verify a response. This method will be called by Dnsruby::SingleResolver
185
+ # before passing a response to the client code.
186
+ # The TSIG record will be removed from packet before passing to client, and
187
+ # the Message#tsigstate and Message#tsigerror will be set accordingly.
188
+ # Message#tsigstate will be set to one of :
189
+ # * :Failed
190
+ # * :Verified
191
+ def verify(query, response, response_bytes, buf="")
192
+ # 4.6. Client processing of answer
193
+ #
194
+ # When a client receives a response from a server and expects to see a
195
+ # TSIG, it first checks if the TSIG RR is present in the response.
196
+ # Otherwise, the response is treated as having a format error and
197
+ # discarded. The client then extracts the TSIG, adjusts the ARCOUNT,
198
+ # and calculates the keyed digest in the same way as the server. If
199
+ # the TSIG does not validate, that response MUST be discarded, unless
200
+ # the RCODE is 9 (NOTAUTH), in which case the client SHOULD attempt to
201
+ # verify the response as if it were a TSIG Error response, as specified
202
+ # in [4.3]. A message containing an unsigned TSIG record or a TSIG
203
+ # record which fails verification SHOULD not be considered an
204
+ # acceptable response; the client SHOULD log an error and continue to
205
+ # wait for a signed response until the request times out.
206
+
207
+ # So, this verify method should simply remove the TSIG RR and calculate
208
+ # the MAC (using original request MAC if required).
209
+ # Should set tsigstate on packet appropriately, and return error.
210
+ # Side effect is packet is stripped of TSIG.
211
+ # Resolver (or client) can then decide what to do...
212
+
213
+ msg_tsig_rr = response.tsig
214
+ if (!verify_common(response))
215
+ return false
216
+ end
217
+
218
+ new_msg_tsig_rr = generate(response, query, buf, response_bytes, msg_tsig_rr)
219
+
220
+ if (msg_tsig_rr.mac == new_msg_tsig_rr.mac)
221
+ response.tsigstate = :Verified
222
+ response.tsigerror = RCode.NOERROR
223
+ return true
224
+ else
225
+ response.tsigstate = :Failed
226
+ response.tsigerror = RCode.BADSIG
227
+ return false
228
+ end
229
+ end
230
+
231
+ def verify_common(response)#:nodoc: all
232
+ tsig_rr = response.tsig
233
+
234
+ if (!tsig_rr)
235
+ response.tsigerror = RCode.FORMERR
236
+ response.tsigstate = :Failed
237
+ return false
238
+ end
239
+
240
+ response.additional.delete(tsig_rr)
241
+ response.header.arcount-=1
242
+
243
+ # First, check the TSIG error in the RR
244
+ if (tsig_rr.error != RCode.NOERROR)
245
+ response.tsigstate = :Failed
246
+ response.tsigerror = tsig_rr.error
247
+ return false
248
+ end
249
+
250
+ if ((tsig_rr.name != @name) || (tsig_rr.algorithm.downcase != @algorithm.downcase))
251
+ Dnsruby.log.error("BADKEY failure")
252
+ response.tsigstate = :Failed
253
+ response.tsigerror = RCode.BADKEY
254
+ return false
255
+ end
256
+
257
+ # Check time_signed (RFC2845, 4.5.2) - only really necessary for server
258
+ if (Time.now.to_i > tsig_rr.time_signed + tsig_rr.fudge ||
259
+ Time.now.to_i < tsig_rr.time_signed - tsig_rr.fudge)
260
+ Dnsruby.log.error("TSIG failed with BADTIME")
261
+ response.tsigstate = :Failed
262
+ response.tsigerror = RCode.BADTIME
263
+ return false
264
+ end
265
+
266
+ return true
267
+ end
268
+
269
+ # Checks TSIG signatures across sessions of multiple DNS envelopes.
270
+ # This method is called each time a new envelope comes in. The envelope
271
+ # is checked - if a TSIG is present, them the stream so far is verified,
272
+ # and the response#tsigstate set to :Verified. If a TSIG is not present,
273
+ # and does not need to be present, then the message is added to the digest
274
+ # stream and the response#tsigstate is set to :Intermediate.
275
+ # If there is an error with the TSIG verification, then the response#tsigstate
276
+ # is set to :Failed.
277
+ # Like verify, this method will only be called by the Dnsruby::SingleResolver
278
+ # class. Client code need not call this method directly.
279
+ def verify_envelope(response, response_bytes)
280
+ # RFC2845 Section 4.4
281
+ # -----
282
+ # A DNS TCP session can include multiple DNS envelopes. This is, for
283
+ # example, commonly used by zone transfer. Using TSIG on such a
284
+ # connection can protect the connection from hijacking and provide data
285
+ # integrity. The TSIG MUST be included on the first and last DNS
286
+ # envelopes. It can be optionally placed on any intermediary
287
+ # envelopes. It is expensive to include it on every envelopes, but it
288
+ # MUST be placed on at least every 100'th envelope. The first envelope
289
+ # is processed as a standard answer, and subsequent messages have the
290
+ # following digest components:
291
+ #
292
+ # * Prior Digest (running)
293
+ # * DNS Messages (any unsigned messages since the last TSIG)
294
+ # * TSIG Timers (current message)
295
+ #
296
+ # This allows the client to rapidly detect when the session has been
297
+ # altered; at which point it can close the connection and retry. If a
298
+ # client TSIG verification fails, the client MUST close the connection.
299
+ # If the client does not receive TSIG records frequently enough (as
300
+ # specified above) it SHOULD assume the connection has been hijacked
301
+ # and it SHOULD close the connection. The client SHOULD treat this the
302
+ # same way as they would any other interrupted transfer (although the
303
+ # exact behavior is not specified).
304
+ # -----
305
+ #
306
+ # Each time a new envelope comes in, this method is called on the QUERY TSIG RR.
307
+ # It will set the response tsigstate to :Verified :Intermediate or :Failed
308
+ # as appropriate.
309
+
310
+ # Keep digest going of messages as they come in (and mark them intermediate)
311
+ # When TSIG comes in, work out what key should be and check. If OK, mark
312
+ # verified. Can reset digest then.
313
+ if (!@buf)
314
+ @num_envelopes = 0
315
+ @last_signed = 0
316
+ end
317
+ @num_envelopes += 1
318
+ if (!response.tsig)
319
+ if ((@num_envelopes > 1) && (@num_envelopes - @last_signed < 100))
320
+ Dnsruby.log.debug("Receiving intermediate envelope in TSIG TCP session")
321
+ response.tsigstate = :Intermediate
322
+ response.tsigerror = RCode.NOERROR
323
+ @buf = @buf + response_bytes
324
+ return
325
+ else
326
+ response.tsigstate = :Failed
327
+ Dnsruby.log.error("Expecting signed packet")
328
+ return false
329
+ end
330
+ end
331
+ @last_signed = @num_envelopes
332
+
333
+ # We have a TSIG - process it!
334
+ tsig = response.tsig
335
+ if (@num_envelopes == 1)
336
+ Dnsruby.log.debug("First response in TSIG TCP session - verifying normally")
337
+ # Process it as a standard answer
338
+ ok = verify(@query, response, response_bytes)
339
+ if (ok)
340
+ mac_bytes = MessageEncoder.new {|m|
341
+ m.put_pack('n', tsig.mac_size)
342
+ m.put_bytes(tsig.mac)
343
+ }.to_s
344
+ @buf = mac_bytes
345
+ end
346
+ return ok
347
+ end
348
+ Dnsruby.log.debug("Processing TSIG on TSIG TCP session")
349
+
350
+ if (!verify_common(response))
351
+ return false
352
+ end
353
+
354
+ # Now add the current message data - remember to frig the arcount
355
+ response_bytes = Header.decrement_arcount_encoded(response_bytes)
356
+ @buf += response_bytes[0, response.tsigstart]
357
+
358
+ # Let's add the timers
359
+ timers_data = MessageEncoder.new { |msg|
360
+ time_high = (tsig.time_signed >> 32)
361
+ time_low = (tsig.time_signed & 0xFFFFFFFF)
362
+ msg.put_pack('nN', time_high, time_low)
363
+ msg.put_pack('n', tsig.fudge)
364
+ }.to_s
365
+ @buf += timers_data
366
+
367
+ mac = calculate_mac(tsig.algorithm, @buf)
368
+
369
+ if (mac != tsig.mac)
370
+ Dnsruby.log.error("TSIG Verify error on TSIG TCP session")
371
+ response.tsigstate = :Failed
372
+ return false
373
+ end
374
+ mac_bytes = MessageEncoder.new {|m|
375
+ m.put_pack('n', mac.length)
376
+ m.put_bytes(mac)
377
+ }.to_s
378
+ @buf=mac_bytes
379
+
380
+ response.tsigstate = :Verified
381
+ response.tsigerror = RCode.NOERROR
382
+ return true
383
+ end
384
+
385
+
386
+ TypeValue = Types::TSIG #:nodoc: all
387
+ ClassValue = nil #:nodoc: all
388
+ ClassHash[[TypeValue, Classes::ANY]] = self #:nodoc: all
389
+
390
+ # Gets or sets the domain name that specifies the name of the algorithm.
391
+ # The only algorithms currently supported are hmac-md5 and hmac-sha1.
392
+ #
393
+ # rr.algorithm=(algorithm_name)
394
+ # print "algorithm = ", rr.algorithm, "\n"
395
+ #
396
+ attr_reader :algorithm
397
+
398
+ # Gets or sets the signing time as the number of seconds since 1 Jan 1970
399
+ # 00:00:00 UTC.
400
+ #
401
+ # The default signing time is the current time.
402
+ #
403
+ # rr.time_signed=(time)
404
+ # print "time signed = ", rr.time_signed, "\n"
405
+ #
406
+ attr_accessor :time_signed
407
+
408
+ # Gets or sets the "fudge", i.e., the seconds of error permitted in the
409
+ # signing time.
410
+ #
411
+ # The default fudge is 300 seconds.
412
+ #
413
+ # rr.fudge=(60)
414
+ # print "fudge = ", rr.fudge, "\n"
415
+ #
416
+ attr_reader :fudge
417
+
418
+ # Returns the number of octets in the message authentication code (MAC).
419
+ # The programmer must call a Net::DNS::Packet object's data method
420
+ # before this will return anything meaningful.
421
+ #
422
+ # print "MAC size = ", rr.mac_size, "\n"
423
+ #
424
+ attr_accessor :mac_size
425
+
426
+ # Returns the message authentication code (MAC) as a string of hex
427
+ # characters. The programmer must call a Net::DNS::Packet object's
428
+ # data method before this will return anything meaningful.
429
+ #
430
+ # print "MAC = ", rr.mac, "\n"
431
+ #
432
+ attr_accessor :mac
433
+
434
+ # Gets or sets the original message ID.
435
+ #
436
+ # rr.original_id(12345)
437
+ # print "original ID = ", rr.original_id, "\n"
438
+ #
439
+ attr_accessor :original_id
440
+
441
+ # Returns the RCODE covering TSIG processing. Common values are
442
+ # NOERROR, BADSIG, BADKEY, and BADTIME. See RFC 2845 for details.
443
+ #
444
+ # print "error = ", rr.error, "\n"
445
+ #
446
+ attr_accessor :error
447
+
448
+ # Returns the length of the Other Data. Should be zero unless the
449
+ # error is BADTIME.
450
+ #
451
+ # print "other len = ", rr.other_size, "\n"
452
+ #
453
+ attr_accessor :other_size
454
+
455
+ # Returns the Other Data. This field should be empty unless the
456
+ # error is BADTIME, in which case it will contain the server's
457
+ # time as the number of seconds since 1 Jan 1970 00:00:00 UTC.
458
+ #
459
+ # print "other data = ", rr.other_data, "\n"
460
+ #
461
+ attr_accessor :other_data
462
+
463
+ # Stores the secret key used for signing/verifying messages.
464
+ attr_accessor :key
465
+
466
+ def init_defaults
467
+ # @TODO@ Have new() method which takes key_name and key?
468
+ @algorithm = DEFAULT_ALGORITHM
469
+ @fudge = DEFAULT_FUDGE
470
+ @mac_size = 0
471
+ @mac = ""
472
+ @original_id = rand(65536)
473
+ @error = 0
474
+ @other_size = 0
475
+ @other_data = ""
476
+ @time_signed = nil
477
+ @buf = nil
478
+
479
+ # RFC 2845 Section 2.3
480
+ @klass = Classes.ANY
481
+
482
+ @ttl = 0 # RFC 2845 Section 2.3
483
+ end
484
+
485
+ def from_data(data) #:nodoc: all
486
+ @algorithm, @time_signed, @fudge, @mac_size, @mac, @original_id, @error, @other_size, @other_data = data
487
+ end
488
+
489
+ def name=(n)
490
+ if (n.instance_of?String)
491
+ n = Name.create(n)
492
+ end
493
+ if (!n.absolute?)
494
+ @name = Name.create(n.to_s + ".")
495
+ else
496
+ @name = n
497
+ end
498
+ end
499
+
500
+ # Create the RR from a standard string
501
+ def from_string(str) #:nodoc: all
502
+ parts = str.split("[:/]")
503
+ if (parts.length < 2 || parts.length > 3)
504
+ raise ArgumentException.new("Invalid TSIG key specification")
505
+ end
506
+ if (parts.length == 3)
507
+ return TSIG.new(parts[0], parts[1], parts[2]);
508
+ else
509
+ return TSIG.new(HMAC_MD5, parts[0], parts[1]);
510
+ end
511
+ end
512
+
513
+ # Set the algorithm to use to generate the HMAC
514
+ # Supported values are :
515
+ # * hmac-md5
516
+ # * hmac-sha1
517
+ # * hmac-sha256
518
+ def algorithm=(alg)
519
+ if (alg.class == String)
520
+ if (alg.downcase=="hmac-md5")
521
+ @algorithm = HMAC_MD5;
522
+ elsif (alg.downcase=="hmac-sha1")
523
+ @algorithm = HMAC_SHA1;
524
+ elsif (alg.downcase=="hmac-sha256")
525
+ @algorithm = HMAC_SHA256;
526
+ else
527
+ raise ArgumentError.new("Invalid TSIG algorithm")
528
+ end
529
+ elsif (alg.class == Name)
530
+ if (alg!=HMAC_MD5 && alg!=HMAC_SHA1 && alg!=HMAC_SHA256)
531
+ raise ArgumentException.new("Invalid TSIG algorithm")
532
+ end
533
+ @algorithm=alg
534
+ else
535
+ raise ArgumentError.new("#{alg.class} not valid type for Dnsruby::RR::TSIG#algorithm= - use String or Name")
536
+ end
537
+ Dnsruby.log.debug{"Using #{@algorithm.to_s} algorithm"}
538
+ end
539
+
540
+ def fudge=(f)
541
+ if (f < 0 || f > 0x7FFF)
542
+ @fudge = DEFAULT_FUDGE
543
+ else
544
+ @fudge = f
545
+ end
546
+ end
547
+
548
+ def rdata_to_string
549
+ rdatastr=""
550
+ if (@algorithm!=nil)
551
+ error = @error
552
+ error = "UNDEFINED" unless error!=nil
553
+ rdatastr = "#{@original_id} #{@time_signed} #{@algorithm.to_s(true)} #{error}";
554
+ if (@other_size > 0 && @other_data!=nil)
555
+ rdatastr += " #{@other_data}"
556
+ end
557
+ rdatastr += " " + mac.unpack("H*").to_s
558
+ end
559
+
560
+ return rdatastr
561
+ end
562
+
563
+ def encode_rdata(msg, canonical=false) #:nodoc: all
564
+ # Name needs to be added with no compression - done in Dnsruby::Message#encode
565
+ msg.put_name(@algorithm.downcase, true)
566
+ time_high = (@time_signed >> 32)
567
+ time_low = (@time_signed & 0xFFFFFFFF)
568
+ msg.put_pack('nN', time_high, time_low)
569
+ msg.put_pack('n', @fudge)
570
+ msg.put_pack('n', @mac_size)
571
+ msg.put_bytes(@mac)
572
+ msg.put_pack('n', @original_id)
573
+ msg.put_pack('n', @error)
574
+ msg.put_pack('n', @other_size)
575
+ msg.put_bytes(@other_data)
576
+ end
577
+
578
+ def self.decode_rdata(msg) #:nodoc: all
579
+ alg=msg.get_name
580
+ time_high, time_low = msg.get_unpack("nN")
581
+ time_signed = (time_high << 32) + time_low
582
+ fudge, = msg.get_unpack("n")
583
+ mac_size, = msg.get_unpack("n")
584
+ mac = msg.get_bytes(mac_size)
585
+ original_id, = msg.get_unpack("n")
586
+ error, = msg.get_unpack("n")
587
+ other_size, = msg.get_unpack("n")
588
+ other_data = msg.get_bytes(other_size)
589
+ return self.new([alg, time_signed, fudge, mac_size, mac, original_id, error, other_size, other_data])
590
+ end
591
+ end
592
+ end
593
+ end