faildns 0.0.1

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