faildns 0.0.1

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 (37) hide show
  1. data/bin/failnamed +203 -0
  2. data/bin/failresolve +37 -0
  3. data/lib/faildns.rb +20 -0
  4. data/lib/faildns/class.rb +96 -0
  5. data/lib/faildns/client.rb +114 -0
  6. data/lib/faildns/common.rb +52 -0
  7. data/lib/faildns/domainname.rb +223 -0
  8. data/lib/faildns/header.rb +254 -0
  9. data/lib/faildns/header/opcode.rb +95 -0
  10. data/lib/faildns/header/status.rb +123 -0
  11. data/lib/faildns/header/type.rb +72 -0
  12. data/lib/faildns/ip.rb +57 -0
  13. data/lib/faildns/message.rb +100 -0
  14. data/lib/faildns/qclass.rb +51 -0
  15. data/lib/faildns/qtype.rb +60 -0
  16. data/lib/faildns/question.rb +101 -0
  17. data/lib/faildns/resourcerecord.rb +140 -0
  18. data/lib/faildns/resourcerecord/IN.rb +29 -0
  19. data/lib/faildns/resourcerecord/IN/A.rb +75 -0
  20. data/lib/faildns/resourcerecord/IN/AAAA.rb +77 -0
  21. data/lib/faildns/resourcerecord/IN/CNAME.rb +70 -0
  22. data/lib/faildns/resourcerecord/IN/HINFO.rb +79 -0
  23. data/lib/faildns/resourcerecord/IN/MX.rb +75 -0
  24. data/lib/faildns/resourcerecord/IN/NS.rb +77 -0
  25. data/lib/faildns/resourcerecord/IN/NULL.rb +66 -0
  26. data/lib/faildns/resourcerecord/IN/PTR.rb +69 -0
  27. data/lib/faildns/resourcerecord/IN/SOA.rb +128 -0
  28. data/lib/faildns/resourcerecord/IN/TXT.rb +63 -0
  29. data/lib/faildns/resourcerecord/data.rb +41 -0
  30. data/lib/faildns/server.rb +71 -0
  31. data/lib/faildns/server/dispatcher.rb +181 -0
  32. data/lib/faildns/server/dispatcher/connectiondispatcher.rb +93 -0
  33. data/lib/faildns/server/dispatcher/event.rb +47 -0
  34. data/lib/faildns/server/dispatcher/eventdispatcher.rb +73 -0
  35. data/lib/faildns/server/dispatcher/socket.rb +93 -0
  36. data/lib/faildns/type.rb +186 -0
  37. metadata +100 -0
@@ -0,0 +1,52 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of faildns.
5
+ #
6
+ # faildns is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # faildns is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with faildns. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ module DNS
21
+ Version = '0.0.1'
22
+
23
+ def self.debug (argument, options={})
24
+ if !ENV['DEBUG']
25
+ return
26
+ end
27
+
28
+ if ENV['DEBUG'].to_i < (options[:level] || 1)
29
+ return
30
+ end
31
+
32
+ output = "From: #{caller[0, options[:deep] || 1].join("\n")}\n"
33
+
34
+ if argument.is_a?(Exception)
35
+ output << "#{argument.class}: #{argument.message}\n"
36
+ output << argument.backtrace.collect {|stack|
37
+ stack
38
+ }.join("\n")
39
+ output << "\n\n"
40
+ elsif argument.is_a?(String)
41
+ output << "#{argument}\n"
42
+ else
43
+ output << "#{argument.inspect}\n"
44
+ end
45
+
46
+ if options[:separator]
47
+ output << options[:separator]
48
+ end
49
+
50
+ puts output
51
+ end
52
+ end
@@ -0,0 +1,223 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of faildns.
5
+ #
6
+ # faildns is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # faildns is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with faildns. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ module DNS
21
+
22
+ #--
23
+ # Domain names in messages are expressed in terms of a sequence of labels.
24
+ # Each label is represented as a one octet length field followed by that
25
+ # number of octets. Since every domain name ends with the null label of
26
+ # the root, a domain name is terminated by a length byte of zero. The
27
+ # high order two bits of every length octet must be zero, and the
28
+ # remaining six bits of the length field limit the label to 63 octets or
29
+ # less.
30
+ #
31
+ # To simplify implementations, the total length of a domain name (i.e.,
32
+ # label octets and label length octets) is restricted to 255 octets or
33
+ # less.
34
+ #
35
+ # Although labels can contain any 8 bit values in octets that make up a
36
+ # label, it is strongly recommended that labels follow the preferred
37
+ # syntax described elsewhere in this memo, which is compatible with
38
+ # existing host naming conventions. Name servers and resolvers must
39
+ # compare labels in a case-insensitive manner (i.e., A=a), assuming ASCII
40
+ # with zero parity. Non-alphabetic codes must match exactly.
41
+ #
42
+ # In order to reduce the size of messages, the domain system utilizes a
43
+ # compression scheme which eliminates the repetition of domain names in a
44
+ # message. In this scheme, an entire domain name or a list of labels at
45
+ # the end of a domain name is replaced with a pointer to a prior occurance
46
+ # of the same name.
47
+ #
48
+ # The pointer takes the form of a two octet sequence:
49
+ #
50
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
51
+ # | 1 1| OFFSET |
52
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
53
+ #
54
+ # The first two bits are ones. This allows a pointer to be distinguished
55
+ # from a label, since the label must begin with two zero bits because
56
+ # labels are restricted to 63 octets or less. (The 10 and 01 combinations
57
+ # are reserved for future use.) The OFFSET field specifies an offset from
58
+ # the start of the message (i.e., the first octet of the ID field in the
59
+ # domain header). A zero offset specifies the first byte of the ID field,
60
+ # etc.
61
+ #
62
+ # The compression scheme allows a domain name in a message to be
63
+ # represented as either:
64
+ #
65
+ # - a sequence of labels ending in a zero octet
66
+ #
67
+ # - a pointer
68
+ #
69
+ # - a sequence of labels ending with a pointer
70
+ #
71
+ # Pointers can only be used for occurances of a domain name where the
72
+ # format is not class specific. If this were not the case, a name server
73
+ # or resolver would be required to know the format of all RRs it handled.
74
+ # As yet, there are no such cases, but they may occur in future RDATA
75
+ # formats.
76
+ #
77
+ # If a domain name is contained in a part of the message subject to a
78
+ # length field (such as the RDATA section of an RR), and compression is
79
+ # used, the length of the compressed name is used in the length
80
+ # calculation, rather than the length of the expanded name.
81
+ #
82
+ # Programs are free to avoid using pointers in messages they generate,
83
+ # although this will reduce datagram capacity, and may cause truncation.
84
+ # However all programs are required to understand arriving messages that
85
+ # contain pointers.
86
+ #
87
+ # For example, a datagram might need to use the domain names F.ISI.ARPA,
88
+ # FOO.F.ISI.ARPA, ARPA, and the root. Ignoring the other fields of the
89
+ # message, these domain names might be represented as:
90
+ #
91
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
92
+ # 20 | 1 | F |
93
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
94
+ # 22 | 3 | I |
95
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
96
+ # 24 | S | I |
97
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
98
+ # 26 | 4 | A |
99
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
100
+ # 28 | R | P |
101
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
102
+ # 30 | A | 0 |
103
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
104
+ #
105
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
106
+ # 40 | 3 | F |
107
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
108
+ # 42 | O | O |
109
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
110
+ # 44 | 1 1| 20 |
111
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
112
+ #
113
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
114
+ # 64 | 1 1| 26 |
115
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
116
+ #
117
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
118
+ # 92 | 0 | |
119
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
120
+ #
121
+ # The domain name for F.ISI.ARPA is shown at offset 20. The domain name
122
+ # FOO.F.ISI.ARPA is shown at offset 40; this definition uses a pointer to
123
+ # concatenate a label for FOO to the previously defined F.ISI.ARPA. The
124
+ # domain name ARPA is defined at offset 64 using a pointer to the ARPA
125
+ # component of the name F.ISI.ARPA at 20; note that this pointer relies on
126
+ # ARPA being the last label in the string at 20. The root domain name is
127
+ # defined by a single octet of zeros at 92; the root domain name has no
128
+ # labels.
129
+ #++
130
+
131
+ class DomainName
132
+ def self.pointer (string, offset)
133
+ string.force_encoding 'BINARY'
134
+
135
+ return string[offset.unpack('n').first & 0x3FFF, 512]
136
+ end
137
+
138
+ def self.parse (string, whole)
139
+ string.force_encoding 'BINARY'
140
+
141
+ result = ''
142
+
143
+ case string.unpack('c').first & 0xC0
144
+ when 0xC0
145
+ result += DomainName.parse(DomainName.pointer(whole, string), whole)
146
+ string[0, 2] = ''
147
+
148
+ when 0x00
149
+ while (length = string.unpack('c').first) != 0 && (length & 0xC0) == 0
150
+ result += '.' + string[1, length]
151
+ string[0, length + 1] = ''
152
+ end
153
+
154
+ if length & 0xC0 == 0xC0
155
+ result += '.' + DomainName.parse(string, whole)
156
+
157
+ string[0, 2] = ''
158
+ else
159
+ string[0, 1] = ''
160
+ end
161
+
162
+ result[0, 1] = ''
163
+ end
164
+
165
+ DomainName.new result
166
+ end
167
+
168
+ def self.length (string)
169
+ string.force_encoding 'BINARY'
170
+
171
+ string = string.clone
172
+ result = 0
173
+
174
+ if string.unpack('c').first & 0xC0 == 0xC0
175
+ return 2
176
+ else
177
+ while (length = string.unpack('c').first) != 0 && (length & 0xC0) == 0
178
+ result += 1 + length
179
+ string[0, length + 1] = ''
180
+ end
181
+
182
+ if length & 0xC0 == 0xC0
183
+ result += 2
184
+ else
185
+ result += 1
186
+ end
187
+ end
188
+
189
+ return result
190
+ end
191
+
192
+ attr_accessor :domain
193
+
194
+ def initialize (domain)
195
+ if domain.is_a? DomainName
196
+ domain = domain.domain
197
+ end
198
+
199
+ if !domain.is_a? String
200
+ raise ArgumentError.new 'The passed value is not a string.'
201
+ end
202
+
203
+ @domain = domain
204
+ end
205
+
206
+ def to_s
207
+ @domain
208
+ end
209
+
210
+ alias to_str to_s
211
+
212
+ def pack (options={})
213
+ result = ''
214
+
215
+ @domain.split('.').each {|part|
216
+ result += [part.length].pack('c') + part
217
+ }
218
+
219
+ result += [0].pack('c')
220
+ end
221
+ end
222
+
223
+ end
@@ -0,0 +1,254 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of faildns.
5
+ #
6
+ # faildns is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # faildns is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with faildns. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'faildns/header/type'
21
+ require 'faildns/header/opcode'
22
+ require 'faildns/header/status'
23
+
24
+ module DNS
25
+
26
+ #--
27
+ # The header contains the following fields:
28
+ #
29
+ # 1 1 1 1 1 1
30
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
31
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
32
+ # | ID |
33
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
34
+ # |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
35
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
36
+ # | QDCOUNT |
37
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
38
+ # | ANCOUNT |
39
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
40
+ # | NSCOUNT |
41
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
42
+ # | ARCOUNT |
43
+ # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
44
+ #
45
+ # where:
46
+ #
47
+ # ID A 16 bit identifier assigned by the program that
48
+ # generates any kind of query. This identifier is copied
49
+ # the corresponding reply and can be used by the requester
50
+ # to match up replies to outstanding queries.
51
+ #
52
+ # QR A one bit field that specifies whether this message is a
53
+ # query (0), or a response (1).
54
+ #
55
+ # OPCODE A four bit field that specifies kind of query in this
56
+ # message. This value is set by the originator of a query
57
+ # and copied into the response. The values are:
58
+ #
59
+ # 0 a standard query (QUERY)
60
+ #
61
+ # 1 an inverse query (IQUERY)
62
+ #
63
+ # 2 a server status request (STATUS)
64
+ #
65
+ # 3-15 reserved for future use
66
+ #
67
+ # AA Authoritative Answer - this bit is valid in responses,
68
+ # and specifies that the responding name server is an
69
+ # authority for the domain name in question section.
70
+ #
71
+ # Note that the contents of the answer section may have
72
+ # multiple owner names because of aliases. The AA bit
73
+ # corresponds to the name which matches the query name, or
74
+ # the first owner name in the answer section.
75
+ #
76
+ # TC TrunCation - specifies that this message was truncated
77
+ # due to length greater than that permitted on the
78
+ # transmission channel.
79
+ #
80
+ # RD Recursion Desired - this bit may be set in a query and
81
+ # is copied into the response. If RD is set, it directs
82
+ # the name server to pursue the query recursively.
83
+ # Recursive query support is optional.
84
+ #
85
+ # RA Recursion Available - this be is set or cleared in a
86
+ # response, and denotes whether recursive query support is
87
+ # available in the name server.
88
+ #
89
+ # Z Reserved for future use. Must be zero in all queries
90
+ # and responses.
91
+ #
92
+ # RCODE Response code - this 4 bit field is set as part of
93
+ # responses. The values have the following
94
+ # interpretation:
95
+ #
96
+ # 0 No error condition
97
+ #
98
+ # 1 Format error - The name server was
99
+ # unable to interpret the query.
100
+ #
101
+ # 2 Server failure - The name server was
102
+ # unable to process this query due to a
103
+ # problem with the name server.
104
+ #
105
+ # 3 Name Error - Meaningful only for
106
+ # responses from an authoritative name
107
+ # server, this code signifies that the
108
+ # domain name referenced in the query does
109
+ # not exist.
110
+ #
111
+ # 4 Not Implemented - The name server does
112
+ # not support the requested kind of query.
113
+ #
114
+ # 5 Refused - The name server refuses to
115
+ # perform the specified operation for
116
+ # policy reasons. For example, a name
117
+ # server may not wish to provide the
118
+ # information to the particular requester,
119
+ # or a name server may not wish to perform
120
+ # a particular operation (e.g., zone
121
+ # transfer) for particular data.
122
+ #
123
+ # 6-15 Reserved for future use.
124
+ #
125
+ # QDCOUNT an unsigned 16 bit integer specifying the number of
126
+ # entries in the question section.
127
+ #
128
+ # ANCOUNT an unsigned 16 bit integer specifying the number of
129
+ # resource records in the answer section.
130
+ #
131
+ # NSCOUNT an unsigned 16 bit integer specifying the number of name
132
+ # server resource records in the authority records
133
+ # section.
134
+ #
135
+ # ARCOUNT an unsigned 16 bit integer specifying the number of
136
+ # resource records in the additional records section.
137
+ #++
138
+
139
+ class Header
140
+ @@default = {
141
+ :AA => false, :TC => false, :RD => false, :RA => false, :AD => false, :CD => true,
142
+
143
+ :RCODE => Status.new(:NOERROR),
144
+
145
+ :QDCOUNT => 0,
146
+ :ANCOUNT => 0,
147
+ :NSCOUNT => 0,
148
+ :ARCOUNT => 0
149
+ }
150
+
151
+ def self.parse (string)
152
+ data = string.unpack('nnnnnn')
153
+
154
+ string[0, Header.length] = ''
155
+
156
+ return Header.new(
157
+ :ID => data[0],
158
+
159
+ :QR => Type.new((data[1] & 0x8000) >> 15),
160
+
161
+ :OPCODE => Opcode.new((data[1] & 0x7800) >> 11),
162
+
163
+ :AA => (data[1] & 0x400 != 0),
164
+ :TC => (data[1] & 0x200 != 0),
165
+ :RD => (data[1] & 0x100 != 0),
166
+ :RA => (data[1] & 0x80 != 0),
167
+ :AD => (data[1] & 0x40 != 0),
168
+ :CD => (data[1] & 0x20 != 0),
169
+
170
+ :RCODE => Status.new(data[1] & 0xf),
171
+
172
+ :QDCOUNT => data[2],
173
+
174
+ :ANCOUNT => data[3],
175
+
176
+ :NSCOUNT => data[4],
177
+
178
+ :ARCOUNT => data[5]
179
+ )
180
+ end
181
+
182
+ def self.length (string=nil)
183
+ 12
184
+ end
185
+
186
+ def initialize (what={})
187
+ if !what.is_a? Hash
188
+ raise ArgumentError.new('You have to pass a Hash.')
189
+ end
190
+
191
+ @data = @@default.merge(what)
192
+
193
+ if block_given?
194
+ yield self
195
+ end
196
+ end
197
+
198
+ def id; @data[:ID] end
199
+ def type; @data[:QR] end
200
+ def class; @data[:OPCODE] end
201
+ def authoritative?; @data[:AA] end
202
+ def truncated?; @data[:TC] end
203
+ def recursive?; @data[:RD] end
204
+ def recursivable?; @data[:RA] end
205
+ def authentic?; @data[:AD] end
206
+ def checking?; @data[:CD] end
207
+ def status; @data[:RCODE] end
208
+ def questions; @data[:QDCOUNT] end
209
+ def answers; @data[:ANCOUNT] end
210
+ def authorities; @data[:NSCOUNT] end
211
+ def additionals; @data[:ARCOUNT] end
212
+
213
+ def id= (val); @data[:ID] = val end
214
+ def type= (val); @data[:QR] = Type.new(val) end
215
+ def class= (val); @data[:OPCODE] = Opcode.new(val) end
216
+ def authoritative!; @data[:AA] = true end
217
+ def truncated!; @data[:TC] = true end
218
+ def recursive!; @data[:RD] = true end
219
+ def recursivable!; @data[:RA] = true end
220
+ def authentic!; @data[:AD] = true end
221
+ def checking!; @data[:CD] = true end
222
+ def not_authoritative!; @data[:AA] = false end
223
+ def not_truncated!; @data[:TC] = false end
224
+ def not_recursive!; @data[:RD] = false end
225
+ def not_recursivable!; @data[:RA] = false end
226
+ def not_authentic!; @data[:AD] = false end
227
+ def not_checking!; @data[:CD] = false end
228
+ def status= (val); @data[:RCODE] = Status.new(val) end
229
+ def questions= (val); @data[:QDCOUNT] = val end
230
+ def answers= (val); @data[:ANCOUNT] = val end
231
+ def authorities= (val); @data[:NSCOUNT] = val end
232
+ def additionals= (val); @data[:ARCOUNT] = val end
233
+
234
+ def pack
235
+ [
236
+ self.id,
237
+
238
+ ( (self.type.value << 15) \
239
+ | (self.class.value << 14) \
240
+ | ((self.authoritative?) ? (1 << 10) : 0) \
241
+ | ((self.truncated?) ? (1 << 9) : 0) \
242
+ | ((self.recursive?) ? (1 << 8) : 0) \
243
+ | ((self.recursivable?) ? (1 << 7) : 0) \
244
+ | (self.status.value)),
245
+
246
+ self.questions,
247
+ self.answers,
248
+ self.authorities,
249
+ self.additionals
250
+ ].pack('nnnnnn')
251
+ end
252
+ end
253
+
254
+ end