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.
- data/bin/failnamed +203 -0
- data/bin/failresolve +37 -0
- data/lib/faildns.rb +20 -0
- data/lib/faildns/class.rb +96 -0
- data/lib/faildns/client.rb +114 -0
- data/lib/faildns/common.rb +52 -0
- data/lib/faildns/domainname.rb +223 -0
- data/lib/faildns/header.rb +254 -0
- data/lib/faildns/header/opcode.rb +95 -0
- data/lib/faildns/header/status.rb +123 -0
- data/lib/faildns/header/type.rb +72 -0
- data/lib/faildns/ip.rb +57 -0
- data/lib/faildns/message.rb +100 -0
- data/lib/faildns/qclass.rb +51 -0
- data/lib/faildns/qtype.rb +60 -0
- data/lib/faildns/question.rb +101 -0
- data/lib/faildns/resourcerecord.rb +140 -0
- data/lib/faildns/resourcerecord/IN.rb +29 -0
- data/lib/faildns/resourcerecord/IN/A.rb +75 -0
- data/lib/faildns/resourcerecord/IN/AAAA.rb +77 -0
- data/lib/faildns/resourcerecord/IN/CNAME.rb +70 -0
- data/lib/faildns/resourcerecord/IN/HINFO.rb +79 -0
- data/lib/faildns/resourcerecord/IN/MX.rb +75 -0
- data/lib/faildns/resourcerecord/IN/NS.rb +77 -0
- data/lib/faildns/resourcerecord/IN/NULL.rb +66 -0
- data/lib/faildns/resourcerecord/IN/PTR.rb +69 -0
- data/lib/faildns/resourcerecord/IN/SOA.rb +128 -0
- data/lib/faildns/resourcerecord/IN/TXT.rb +63 -0
- data/lib/faildns/resourcerecord/data.rb +41 -0
- data/lib/faildns/server.rb +71 -0
- data/lib/faildns/server/dispatcher.rb +181 -0
- data/lib/faildns/server/dispatcher/connectiondispatcher.rb +93 -0
- data/lib/faildns/server/dispatcher/event.rb +47 -0
- data/lib/faildns/server/dispatcher/eventdispatcher.rb +73 -0
- data/lib/faildns/server/dispatcher/socket.rb +93 -0
- data/lib/faildns/type.rb +186 -0
- metadata +100 -0
data/bin/failnamed
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
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
|
+
require 'faildns/server'
|
20
|
+
require 'faildns/client'
|
21
|
+
require 'optimus'
|
22
|
+
require 'rexml/document'
|
23
|
+
|
24
|
+
opt = Optimus.new {|o|
|
25
|
+
o.set(
|
26
|
+
:type => :numeric,
|
27
|
+
|
28
|
+
:long => 'port',
|
29
|
+
:short => 'p',
|
30
|
+
|
31
|
+
:default => 53
|
32
|
+
)
|
33
|
+
|
34
|
+
o.set(
|
35
|
+
:type => :string,
|
36
|
+
|
37
|
+
:long => 'host',
|
38
|
+
:short => 'h',
|
39
|
+
|
40
|
+
:default => '0.0.0.0'
|
41
|
+
)
|
42
|
+
|
43
|
+
o.set(
|
44
|
+
:type => :string,
|
45
|
+
|
46
|
+
:long => 'configuration',
|
47
|
+
:short => 'c'
|
48
|
+
)
|
49
|
+
}
|
50
|
+
|
51
|
+
$config = REXML::Document.new(File.new(opt.params[:c]))
|
52
|
+
|
53
|
+
$server = DNS::Server.new {|s|
|
54
|
+
s.options[:host] = ($config.elements.each('//bind/host') {}.first.text rescue nil) || opt.params[:host]
|
55
|
+
s.options[:port] = ($config.elements.each('//bind/port') {}.first.text rescue nil) || opt.params[:port]
|
56
|
+
}
|
57
|
+
|
58
|
+
$client = DNS::Client.new {|c|
|
59
|
+
$config.elements.each('//servers/server') {|server|
|
60
|
+
c.servers.push server.text
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
$options = {}
|
65
|
+
|
66
|
+
class Domain
|
67
|
+
attr_accessor :type, :matches, :location
|
68
|
+
|
69
|
+
def initialize
|
70
|
+
if block_given?
|
71
|
+
yield self
|
72
|
+
end
|
73
|
+
|
74
|
+
self.normalize
|
75
|
+
end
|
76
|
+
|
77
|
+
def match (domain)
|
78
|
+
domain.to_s.match(@regexp)
|
79
|
+
end
|
80
|
+
|
81
|
+
def normalize
|
82
|
+
case @type
|
83
|
+
when 'regexp'
|
84
|
+
@regexp = Regexp.new(@matches)
|
85
|
+
|
86
|
+
else
|
87
|
+
@regexp = Regexp.new('^' + Regexp.escape(@matches).gsub(/\\\*/, '.*').gsub(/\\\?/, '.') + '$')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
$options[:domains] = []
|
93
|
+
|
94
|
+
$config.elements.each('//domains/domain') {|domain|
|
95
|
+
$options[:domains].push Domain.new {|d|
|
96
|
+
d.type = domain.attributes['type']
|
97
|
+
d.matches = domain.attributes['matches']
|
98
|
+
d.location = domain.text.strip
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
module Commands
|
103
|
+
module IN
|
104
|
+
def self.A (message)
|
105
|
+
question = message.questions.first
|
106
|
+
|
107
|
+
ip = $options[:domains].find {|domain|
|
108
|
+
domain.match(question.name)
|
109
|
+
}.location rescue nil
|
110
|
+
|
111
|
+
if !ip
|
112
|
+
ip = $client.resolve(question.name)
|
113
|
+
else
|
114
|
+
# if it's not an IP but a domain, do magic
|
115
|
+
if !(IPAddr.new(ip) rescue false)
|
116
|
+
tmp = $options[:domains].find {|domain|
|
117
|
+
domain.match(ip)
|
118
|
+
}
|
119
|
+
|
120
|
+
if !tmp
|
121
|
+
ip = $client.resolve(ip)
|
122
|
+
else
|
123
|
+
ip = tmp.location
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
if !ip
|
129
|
+
return DNS::Message.new(
|
130
|
+
DNS::Header.new {|h|
|
131
|
+
h.id = message.header.id
|
132
|
+
|
133
|
+
h.type = :RESPONSE
|
134
|
+
h.class = :QUERY
|
135
|
+
|
136
|
+
h.status = :NXDOMAIN
|
137
|
+
|
138
|
+
h.questions = 1
|
139
|
+
},
|
140
|
+
|
141
|
+
[question]
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
return DNS::Message.new(
|
146
|
+
DNS::Header.new {|h|
|
147
|
+
h.id = message.header.id
|
148
|
+
|
149
|
+
h.type = :RESPONSE
|
150
|
+
h.class = :QUERY
|
151
|
+
|
152
|
+
h.status = :NOERROR
|
153
|
+
|
154
|
+
h.questions = 1
|
155
|
+
h.answers = 1
|
156
|
+
},
|
157
|
+
|
158
|
+
[question],
|
159
|
+
|
160
|
+
[question].map {|question|
|
161
|
+
DNS::ResourceRecord.new {|r|
|
162
|
+
r.name = question.name
|
163
|
+
r.type = :A
|
164
|
+
r.class = :IN
|
165
|
+
|
166
|
+
r.ttl = 3600
|
167
|
+
|
168
|
+
r.length = DNS::ResourceRecord::IN::A.length
|
169
|
+
r.data = DNS::ResourceRecord::IN::A.new(ip)
|
170
|
+
}
|
171
|
+
}
|
172
|
+
)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
$server.register :input, lambda {|socket, message|
|
178
|
+
question = message.questions.first
|
179
|
+
method = Commands.const_get(question.class.to_sym).method(question.type.to_sym) rescue nil
|
180
|
+
|
181
|
+
if !method
|
182
|
+
socket.send DNS::Message.new(
|
183
|
+
DNS::Header.new {|h|
|
184
|
+
h.id = message.header.id
|
185
|
+
|
186
|
+
h.type = :RESPONSE
|
187
|
+
h.class = :QUERY
|
188
|
+
|
189
|
+
h.status = :NOTIMP
|
190
|
+
|
191
|
+
h.questions = 1
|
192
|
+
},
|
193
|
+
|
194
|
+
[question]
|
195
|
+
)
|
196
|
+
|
197
|
+
return
|
198
|
+
end
|
199
|
+
|
200
|
+
socket.send method.call(message)
|
201
|
+
}
|
202
|
+
|
203
|
+
$server.start
|
data/bin/failresolve
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
|
4
|
+
#
|
5
|
+
# This file is part of faildns.
|
6
|
+
#
|
7
|
+
# faildns is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Affero General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# faildns is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Affero General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
18
|
+
# along with faildns. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#++
|
20
|
+
|
21
|
+
require 'faildns/client'
|
22
|
+
require 'optimus'
|
23
|
+
|
24
|
+
opt = Optimus.new {|o|
|
25
|
+
o.set(
|
26
|
+
:type => :array,
|
27
|
+
|
28
|
+
:long => 'servers',
|
29
|
+
:short => 's'
|
30
|
+
)
|
31
|
+
}
|
32
|
+
|
33
|
+
client = DNS::Client.new(:servers => opt.params[:s])
|
34
|
+
|
35
|
+
opt.args.each {|arg|
|
36
|
+
puts "#{arg}: #{client.resolve arg}"
|
37
|
+
}
|
data/lib/faildns.rb
ADDED
@@ -0,0 +1,20 @@
|
|
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/message'
|
@@ -0,0 +1,96 @@
|
|
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
|
+
# CLASS fields appear in resource records. The following CLASS mnemonics
|
24
|
+
# and values are defined:
|
25
|
+
#
|
26
|
+
# IN 1 the Internet
|
27
|
+
#
|
28
|
+
# CS 2 the CSNET class (Obsolete - used only for examples in
|
29
|
+
# some obsolete RFCs)
|
30
|
+
#
|
31
|
+
# CH 3 the CHAOS class
|
32
|
+
#
|
33
|
+
# HS 4 Hesiod [Dyer 87]
|
34
|
+
#++
|
35
|
+
|
36
|
+
class Class
|
37
|
+
Values = {
|
38
|
+
1 => :IN,
|
39
|
+
2 => :CS,
|
40
|
+
3 => :CH,
|
41
|
+
4 => :HS
|
42
|
+
}
|
43
|
+
|
44
|
+
def self.parse (string)
|
45
|
+
string.force_encoding 'BINARY'
|
46
|
+
|
47
|
+
result = self.new(string.unpack('n').first)
|
48
|
+
string[0, self.length] = ''
|
49
|
+
|
50
|
+
return result
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.length (string=nil)
|
54
|
+
2
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :value
|
58
|
+
|
59
|
+
def initialize (value)
|
60
|
+
if value.is_a? Symbol
|
61
|
+
@value = Values.find {|key, val| val == value}.first rescue nil
|
62
|
+
elsif value.is_a? Integer
|
63
|
+
@value = value
|
64
|
+
else
|
65
|
+
@value = value.value rescue nil
|
66
|
+
end
|
67
|
+
|
68
|
+
if !self.to_sym
|
69
|
+
raise ArgumentError.new('The passed value is not a suitable class.')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def pack
|
74
|
+
[@value].pack('n')
|
75
|
+
end
|
76
|
+
|
77
|
+
def == (what)
|
78
|
+
if what.is_a? Symbol
|
79
|
+
self.to_sym == what
|
80
|
+
elsif value.is_a? Integer
|
81
|
+
@value == what
|
82
|
+
else
|
83
|
+
@value == what.value rescue false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_sym
|
88
|
+
Values[@value]
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
Values[@value].to_s
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,114 @@
|
|
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 'timeout'
|
21
|
+
require 'socket'
|
22
|
+
require 'faildns/message'
|
23
|
+
|
24
|
+
module DNS
|
25
|
+
|
26
|
+
class Client
|
27
|
+
attr_reader :options, :servers
|
28
|
+
|
29
|
+
def initialize (options={})
|
30
|
+
@options = options
|
31
|
+
|
32
|
+
@servers = @options[:servers] || []
|
33
|
+
|
34
|
+
if block_given?
|
35
|
+
yield self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def resolve (domain, timeout=10, tries=3)
|
40
|
+
result = nil
|
41
|
+
socket = UDPSocket.new
|
42
|
+
|
43
|
+
id = (rand * 100000).to_i % 65536
|
44
|
+
|
45
|
+
1.upto(tries) {
|
46
|
+
@servers.each {|server|
|
47
|
+
socket.connect(server.to_s, 53)
|
48
|
+
|
49
|
+
socket.print Message.new(
|
50
|
+
Header.new {|h|
|
51
|
+
h.id = id
|
52
|
+
|
53
|
+
h.type = :QUERY
|
54
|
+
h.class = :QUERY
|
55
|
+
|
56
|
+
h.recursive!
|
57
|
+
|
58
|
+
h.questions = 1
|
59
|
+
},
|
60
|
+
|
61
|
+
[Question.new {|q|
|
62
|
+
q.name = domain
|
63
|
+
|
64
|
+
q.class = :IN
|
65
|
+
q.type = :A
|
66
|
+
}]
|
67
|
+
).pack
|
68
|
+
|
69
|
+
if (tmp = Timeout.timeout(timeout) { socket.recvfrom(512) } rescue nil)
|
70
|
+
DNS.debug tmp, { :level => 9 }
|
71
|
+
|
72
|
+
tmp = Message.parse(tmp[0])
|
73
|
+
|
74
|
+
if tmp.header.status == :NXDOMAIN
|
75
|
+
result = false
|
76
|
+
break
|
77
|
+
end
|
78
|
+
|
79
|
+
if tmp.header.status == :NOERROR && tmp.header.id == id
|
80
|
+
result = tmp.answers.find {|answer| answer.type == :A}.data.to_s rescue nil
|
81
|
+
break
|
82
|
+
end
|
83
|
+
end
|
84
|
+
}
|
85
|
+
|
86
|
+
if !result.nil?
|
87
|
+
break
|
88
|
+
end
|
89
|
+
}
|
90
|
+
|
91
|
+
return result
|
92
|
+
end
|
93
|
+
|
94
|
+
def query (message, timeout=10)
|
95
|
+
result = []
|
96
|
+
socket = UDPSocket.new
|
97
|
+
|
98
|
+
@servers.each {|server|
|
99
|
+
socket.connect(server.to_s, 53)
|
100
|
+
|
101
|
+
socket.print message.pack
|
102
|
+
|
103
|
+
if (tmp = Timeout.timeout(timeout) { socket.recvfrom(512) } rescue nil)
|
104
|
+
DNS.debug tmp, { :level => 9 }
|
105
|
+
|
106
|
+
result.push Message.parse(tmp[0])
|
107
|
+
end
|
108
|
+
}
|
109
|
+
|
110
|
+
return result
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|