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,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