dns-zone2 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/HISTORY.md +45 -0
  4. data/README.md +151 -0
  5. data/Rakefile +15 -0
  6. data/dns-zone2.gemspec +41 -0
  7. data/lib/dns/zone.rb +207 -0
  8. data/lib/dns/zone/rr.rb +87 -0
  9. data/lib/dns/zone/rr/a.rb +21 -0
  10. data/lib/dns/zone/rr/aaaa.rb +5 -0
  11. data/lib/dns/zone/rr/cdnskey.rb +5 -0
  12. data/lib/dns/zone/rr/cds.rb +5 -0
  13. data/lib/dns/zone/rr/cname.rb +21 -0
  14. data/lib/dns/zone/rr/dlv.rb +5 -0
  15. data/lib/dns/zone/rr/dnskey.rb +38 -0
  16. data/lib/dns/zone/rr/ds.rb +38 -0
  17. data/lib/dns/zone/rr/hinfo.rb +31 -0
  18. data/lib/dns/zone/rr/mx.rb +33 -0
  19. data/lib/dns/zone/rr/naptr.rb +44 -0
  20. data/lib/dns/zone/rr/ns.rb +21 -0
  21. data/lib/dns/zone/rr/nsec.rb +32 -0
  22. data/lib/dns/zone/rr/nsec3.rb +45 -0
  23. data/lib/dns/zone/rr/nsec3param.rb +38 -0
  24. data/lib/dns/zone/rr/ptr.rb +21 -0
  25. data/lib/dns/zone/rr/record.rb +88 -0
  26. data/lib/dns/zone/rr/rrsig.rb +54 -0
  27. data/lib/dns/zone/rr/soa.rb +51 -0
  28. data/lib/dns/zone/rr/spf.rb +5 -0
  29. data/lib/dns/zone/rr/srv.rb +38 -0
  30. data/lib/dns/zone/rr/sshfp.rb +35 -0
  31. data/lib/dns/zone/rr/txt.rb +24 -0
  32. data/lib/dns/zone/test_case.rb +27 -0
  33. data/lib/dns/zone/version.rb +6 -0
  34. data/test/rr/a_test.rb +37 -0
  35. data/test/rr/aaaa_test.rb +27 -0
  36. data/test/rr/cdnskey_test.rb +31 -0
  37. data/test/rr/cds_test.rb +28 -0
  38. data/test/rr/cname_test.rb +19 -0
  39. data/test/rr/dlv_test.rb +28 -0
  40. data/test/rr/dnskey_test.rb +31 -0
  41. data/test/rr/ds_test.rb +28 -0
  42. data/test/rr/hinfo_test.rb +44 -0
  43. data/test/rr/mx_test.rb +26 -0
  44. data/test/rr/naptr_test.rb +60 -0
  45. data/test/rr/ns_test.rb +18 -0
  46. data/test/rr/nsec3_test.rb +33 -0
  47. data/test/rr/nsec3param_test.rb +29 -0
  48. data/test/rr/nsec_test.rb +24 -0
  49. data/test/rr/ptr_test.rb +19 -0
  50. data/test/rr/record_test.rb +37 -0
  51. data/test/rr/rrsig_test.rb +40 -0
  52. data/test/rr/soa_test.rb +34 -0
  53. data/test/rr/spf_test.rb +20 -0
  54. data/test/rr/srv_test.rb +24 -0
  55. data/test/rr/sshfp_test.rb +24 -0
  56. data/test/rr/txt_test.rb +44 -0
  57. data/test/rr_test.rb +50 -0
  58. data/test/version_test.rb +9 -0
  59. data/test/zone_test.rb +273 -0
  60. metadata +217 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6f75671d72916d9ca1b4905cd91300b37126d716
4
+ data.tar.gz: b5a7f7f754bc0c3145efd379c29812dcee7f365f
5
+ SHA512:
6
+ metadata.gz: 7b05d6fcc4b0209db6c000e7febc9cec4c55fa6d3d5caae968a34540f8ece38546f04ca4bf130fdebbbd4c9008fe7f636b554d53002966124e062304656a9b8e
7
+ data.tar.gz: 3f3f32885b598a05e9bdc64d34264c85e4fe55a173038380e7f8292e20a332d479d34a302212917faeaf04272e0c11a9c7a5bc36ec449dbc90dc70de185bec26
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,45 @@
1
+ ## HEAD
2
+
3
+ ## 0.3.1 (2015-12-23)
4
+
5
+ * Fix edge case when zone file has multiple `ORIGIN` directives.
6
+
7
+ ## 0.3.0 (2015-12-23)
8
+
9
+ * Requires Ruby version >= 2.0
10
+ * Ability to parse zone file with multiple `ORIGIN` directives.
11
+
12
+ ## 0.2.0 (2015-10-20)
13
+
14
+ Development sponsored by Peter J. Philipp [centroid.eu](http://centroid.eu)
15
+
16
+ * Add support for DNSSEC focused RR Types:
17
+ - RFC 3403: NAPTR
18
+ - RFC 4255: SSHFP
19
+ - RFC 4034: DNSKEY, DS, RRSIG, NSEC
20
+ - RFC 7344: CDNSKEY, CDS
21
+ - RFC 4431: DLV
22
+ - RFC 5155: NSEC3, NSEC3PARAM
23
+ * Allow unqualified `domain-name` labels.
24
+ * Allow `ORIGIN` to be specified as an optional parameter when loading a zone, e.g. `zone = DNS::Zone.load(zone_as_string, 'example.com.')`
25
+
26
+ # 0.1.4 (no gem release)
27
+
28
+ * Add helper method to quickly access (or create) SOA.
29
+ * Add `dump_pretty` method to `DNS::Zone`.
30
+
31
+ ## 0.1.3 (2014-10-21)
32
+
33
+ * Fix TXT record parsing bug, when quote enclosed RDATA contained semicolons.
34
+
35
+ ## 0.1.1 (2014-03-30)
36
+
37
+ * Remove `required_ruby_version` from gemspec.
38
+
39
+ ## 0.1.0 (2014-03-30)
40
+
41
+ * Initial non-alpha release with support for common resource records.
42
+
43
+ ## 0.0.0 (2014-02-16)
44
+
45
+ * Initial development/hacking initiated.
@@ -0,0 +1,151 @@
1
+ dns-zone
2
+ ========
3
+
4
+ [![Build Status](https://secure.travis-ci.org/lantins/dns-zone.png?branch=master)](http://travis-ci.org/lantins/dns-zone)
5
+ [![Gem Version](https://badge.fury.io/rb/dns-zone.png)](http://badge.fury.io/rb/dns-zone)
6
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/lantins/dns-zone/blob/master/LICENSE)
7
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/lantins/dns-zone/master/frames)
8
+
9
+ A Ruby library for building, parsing and manipulating DNS zone files.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your Gemfile:
14
+
15
+ gem 'dns-zone'
16
+
17
+ And then execute:
18
+
19
+ bundle install
20
+
21
+ Require the gem in your code:
22
+
23
+ require 'dns/zone'
24
+
25
+ ## Usage
26
+
27
+ ### Loading a zone file
28
+
29
+ zone = DNS::Zone.load(zone_as_string)
30
+
31
+ ### Loading a zone file, without an `$ORIGIN` directive
32
+
33
+ zone = DNS::Zone.load(zone_as_string, "example.com.")
34
+
35
+ ### Creating a new zone programmatically
36
+
37
+ zone = DNS::Zone.new
38
+ zone.origin = 'example.com.'
39
+ zone.ttl = '1d'
40
+
41
+ # quick access to SOA RR
42
+ zone.soa.nameserver = 'ns0.lividpenguin.com.'
43
+ zone.soa.email = 'hostmaster.lividpenguin.com.'
44
+
45
+ # add an A RR
46
+ rec = DNS::Zone::RR::A.new
47
+ rec.address = '127.0.0.1'
48
+ zone.records << rec
49
+
50
+ # output using dns zone file format
51
+ zone.dump
52
+
53
+ # Development
54
+
55
+ ## Development Commands
56
+
57
+ # install external gem dependencies first
58
+ bundle install
59
+
60
+ # run all tests and build code coverage
61
+ bundle exec rake test
62
+
63
+ # hints where to improve docs
64
+ bundle exec inch
65
+
66
+ # watch for changes and run development commands (tests, documentation, etc)
67
+ bundle exec guard
68
+
69
+ # Acknowledgement
70
+
71
+ Special thanks to Peter J. Philipp [centroid.eu](http://centroid.eu) for sponsoring the 0.2.0 release of dns-zone.
72
+
73
+ ---
74
+
75
+ # TODO
76
+
77
+ ## Must have
78
+
79
+ [x] Ability to load a zone made of multiple RR's
80
+ [x] Add support for RR Type: SOA
81
+ [x] Add support for RR Type: NS
82
+ [x] Add support for RR Type: MX
83
+ [x] Add support for RR Type: AAAA
84
+ [x] Add support for RR Type: A
85
+ [x] Add support for RR Type: CNAME
86
+ [x] Add support for RR Type: TXT
87
+ [x] Add support for RR Type: SRV
88
+ [x] Add support for RR Type: PTR
89
+ [x] Add support for RR Type: SPF
90
+ [x] Add support for RR Type: HINFO
91
+ [x] Support loading zone where some records have an empty label
92
+
93
+ [x] Add support for RR Type: NAPTR (RFC 3403)
94
+ [x] Add support for RR Type: SSHFP (RFC 4255)
95
+
96
+ [ ] Add test using real bind zone file, with DNSSEC RR's.
97
+ [ ] Add support for DNSSEC (RFC 4034) RR Types:
98
+ [x] DNSKEY
99
+ [ ] Algorithm may be integer or mnemonic.
100
+ [x] RRSIG
101
+ [ ] Algorithm may be integer or mnemonic.
102
+ [x] NSEC
103
+ [ ] Handle "Type Bit Maps" better, much better...
104
+ [x] DS
105
+ [ ] Add support for DNSSEC (RFC 5155) RR Types:
106
+ [x] NSEC3
107
+ [x] NSEC3PARAM
108
+ [ ] Correctly handle "Presentation Format" as defined in RFC.
109
+
110
+ [x] Add support for DNSSEC (RFC 4431 & RFC 7344) RR Types:
111
+ [x] CDNSKEY (identical to DNSKEY)
112
+ [x] CDS (identical to DS)
113
+ [x] DLV (identical to DS)
114
+
115
+ [ ] Look at newly added DNSSEC RR's and rename fields to be more appropriate, where required.
116
+
117
+ ## Would be nice
118
+
119
+ [ ] Basic validation, error checking:
120
+ [ ] Only one SOA per zone.
121
+ [ ] CNAMEs can't use a label of `@`.
122
+ [ ] PTR zones have some extra conditions:
123
+ [ ] labels cant be repeated
124
+ [ ] names should end in a dot, otherwise they are invalid after expansion
125
+ [ ] IPv4 and IPv6 cant be mixed
126
+
127
+ [ ] Ability to 'include' defaults/records into a zone.
128
+ This may or may not mean supporting the `$INCLUDE` directive.
129
+
130
+ ## At some point; low priority
131
+
132
+ [ ] Configuration options:
133
+ [ ] spaces/tabs used between RR params in zone file output
134
+ [ ] time format used in output (should parse both formats, seconds or bind time format (e.g. 1d))
135
+ [ ] add comments to explain TTL's that are in seconds
136
+ [ ] Ability to add comment to RR (n.b. currently we strip comments when parsing)
137
+ [ ] Add support for RR Type: LOC (RFC 1876)
138
+ [ ] Add support for RR Type: DNAME (RFC 2672)
139
+ [ ] Add support for RR Type: KEY
140
+ [ ] Add support for RR Type: RP
141
+ [ ] Add support for RR Type: RT
142
+
143
+ # Misc. Development Notes
144
+
145
+ - RR Format: `[<TTL>] [<class>] <type> <RDATA>`
146
+ - A DNS zone is built from RR's and a couple of other special directives.
147
+ - If zone file does not include $ORIGIN, it will be inferred by the `zone "<zone-name>" {}` clause from bind.conf
148
+ In general we should always explicitly define an $ORIGIN directive unless there is a very good reason not to.
149
+ - [RFC 1035 - Domain Names - Implementation and Specification](http://www.ietf.org/rfc/rfc1035.txt)
150
+ - [RFC 2308 - Negative Caching of DNS Queries (DNS NCACHE)](http://www.ietf.org/rfc/rfc2308.txt)
151
+ - [RFC 3596 - DNS Extensions to Support IP Version 6](http://www.ietf.org/rfc/rfc3596.txt)
@@ -0,0 +1,15 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'bundler'
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+
6
+ # by default run unit tests.
7
+ task :default => 'test'
8
+
9
+ desc 'Run full test suite and generate code coverage -- COVERAGE=false to disable code coverage'
10
+ Rake::TestTask.new(:test) do |task|
11
+ ENV['COVERAGE'] ||= 'yes'
12
+ task.libs << 'test'
13
+ task.pattern = 'test/**/*_test.rb'
14
+ task.verbose = true
15
+ end
@@ -0,0 +1,41 @@
1
+ require './lib/dns/zone/version'
2
+
3
+ spec = Gem::Specification.new do |s|
4
+ # gem information/details
5
+ s.name = 'dns-zone2'
6
+ s.version = DNS::Zone::Version
7
+ s.date = Time.now.strftime('%Y-%m-%d')
8
+ s.summary = 'A Ruby library for building and parsing DNS zone files.'
9
+ s.license = 'MIT'
10
+ s.homepage = 'https://github.com/hetznerZA/dns-zone2'
11
+ s.authors = ['Hetzner']
12
+ s.email = ['seals@hetzner.co.za ']
13
+
14
+ # gem settings for what files to include.
15
+ s.files = %w(Rakefile README.md HISTORY.md Gemfile dns-zone2.gemspec)
16
+ s.files += Dir.glob('{test/**/*,lib/**/*}')
17
+ s.require_paths = ['lib']
18
+ #s.executables = ['dns-zone']
19
+ #s.default_executable = 'dns-zone'
20
+
21
+ # min ruby version
22
+ s.required_ruby_version = ::Gem::Requirement.new(">= 2.0")
23
+
24
+ # cross platform gem dependencies
25
+ #s.add_dependency('gli')
26
+ #s.add_dependency('paint')
27
+ s.add_development_dependency('bundler', '~> 1.0')
28
+ s.add_development_dependency('rake', '>= 9.0')
29
+ s.add_development_dependency('minitest', '~> 5.0')
30
+ s.add_development_dependency('simplecov', '~> 0.7.1')
31
+ s.add_development_dependency('yard', '~> 0.8')
32
+ s.add_development_dependency('inch', '~> 0.6')
33
+ s.add_development_dependency('guard-minitest', '~> 2.0')
34
+ s.add_development_dependency('guard-bundler', '~> 2.0')
35
+
36
+ # long description.
37
+ s.description = <<-EOL
38
+ A Ruby library for building and parsing DNS zone files for use with
39
+ Bind and PowerDNS (with Bind backend) DNS servers.
40
+ EOL
41
+ end
@@ -0,0 +1,207 @@
1
+ require 'dns/zone/rr'
2
+ require 'dns/zone/version'
3
+
4
+ # :nodoc:
5
+ module DNS
6
+
7
+ # Represents a 'whole' zone of many resource records (RRs).
8
+ #
9
+ # This is also the primary namespace for the `dns-zone` gem.
10
+ class Zone
11
+
12
+ # The default $TTL (directive) of the zone.
13
+ attr_accessor :ttl
14
+ # The primary $ORIGIN (directive) of the zone.
15
+ attr_accessor :origin
16
+ # Array of all the zones RRs (including the SOA).
17
+ attr_accessor :records
18
+
19
+ # Create an empty instance of a DNS zone that you can drive programmatically.
20
+ #
21
+ # @api public
22
+ def initialize
23
+ @records = []
24
+ soa = DNS::Zone::RR::SOA.new
25
+ # set a couple of defaults on the SOA
26
+ soa.serial = Time.now.utc.strftime("%Y%m%d01")
27
+ soa.refresh_ttl = '3h'
28
+ soa.retry_ttl = '15m'
29
+ soa.expiry_ttl = '4w'
30
+ soa.minimum_ttl = '30m'
31
+ end
32
+
33
+ # Helper method to access the zones SOA RR.
34
+ #
35
+ # @api public
36
+ def soa
37
+ # return the first SOA we find in the records array.
38
+ rr = @records.find { |rr| rr.type == "SOA" }
39
+ return rr if rr
40
+ # otherwise create a new SOA
41
+ rr = DNS::Zone::RR::SOA.new
42
+ rr.serial = Time.now.utc.strftime("%Y%m%d01")
43
+ rr.refresh_ttl = '3h'
44
+ rr.retry_ttl = '15m'
45
+ rr.expiry_ttl = '4w'
46
+ rr.minimum_ttl = '30m'
47
+ # store and return new SOA
48
+ @records << rr
49
+ return rr
50
+ end
51
+
52
+ # Generates output of the zone and its records.
53
+ #
54
+ # @api public
55
+ def dump
56
+ content = []
57
+
58
+ @records.each do |rr|
59
+ content << rr.dump
60
+ end
61
+
62
+ content.join("\n") << "\n"
63
+ end
64
+
65
+ # Generates pretty output of the zone and its records.
66
+ #
67
+ # @api public
68
+ def dump_pretty
69
+ content = []
70
+
71
+ last_type = "SOA"
72
+ sorted_records.each do |rr|
73
+ content << '' if last_type != rr.type
74
+ content << rr.dump
75
+ last_type = rr.type
76
+ end
77
+
78
+ content.join("\n") << "\n"
79
+ end
80
+
81
+ # Load the provided zone file data into a new DNS::Zone object.
82
+ #
83
+ # @api public
84
+ def self.load(string, default_origin = "")
85
+ # get entries
86
+ entries = self.extract_entries(string)
87
+
88
+ instance = self.new
89
+
90
+ options = {}
91
+ entries.each do |entry|
92
+ # read in special statments like $TTL and $ORIGIN
93
+ if entry =~ /\$(ORIGIN|TTL)\s+(.+)/
94
+ instance.ttl = $2 if $1 == 'TTL'
95
+ if $1 == 'ORIGIN'
96
+ instance.origin ||= $2
97
+ options[:origin] ||= $2
98
+ options[:last_origin] = $2
99
+ end
100
+ next
101
+ end
102
+
103
+ # parse each RR and create a Ruby object for it
104
+ if entry =~ DNS::Zone::RR::REGEX_RR
105
+ rec = DNS::Zone::RR.load(entry, options)
106
+ next unless rec
107
+ instance.records << rec
108
+ options[:last_label] = rec.label
109
+ end
110
+ end
111
+
112
+ # use default_origin if we didn't see a ORIGIN directive in the zone
113
+ if instance.origin.to_s.empty? && !default_origin.empty?
114
+ instance.origin = default_origin
115
+ end
116
+
117
+ return instance
118
+ end
119
+
120
+ # Extract entries from a zone file that will be later parsed as RRs.
121
+ #
122
+ # @api private
123
+ def self.extract_entries(string)
124
+ # FROM RFC:
125
+ # The format of these files is a sequence of entries. Entries are
126
+ # predominantly line-oriented, though parentheses can be used to continue
127
+ # a list of items across a line boundary, and text literals can contain
128
+ # CRLF within the text. Any combination of tabs and spaces act as a
129
+ # delimiter between the separate items that make up an entry. The end of
130
+ # any line in the master file can end with a comment. The comment starts
131
+ # with a ";" (semicolon).
132
+
133
+ entries = []
134
+ mode = :line
135
+ entry = ''
136
+
137
+ parentheses_ref_count = 0
138
+
139
+ string.lines.each do |line|
140
+ # strip comments unless escaped
141
+ # strip comments, unless its escaped.
142
+ # skip semicolons within "quote segments" (TXT records)
143
+ line = line.gsub(/((?<!\\);)(?=(?:[^"]|"[^"]*")*$).*/o, "").chomp
144
+
145
+ next if line.gsub(/\s+/, '').empty?
146
+
147
+ # append to entry line
148
+ entry << line
149
+
150
+ quotes = entry.count('"')
151
+ has_quotes = quotes > 0
152
+
153
+ parentheses = entry.count('()')
154
+ has_parentheses = parentheses > 0
155
+
156
+ if has_quotes
157
+ character_strings = entry.scan(/("(?:[^"\\]+|\\.)*")/).join(' ')
158
+ without = entry.gsub(/"((?:[^"\\]+|\\.)*)"/, '')
159
+ parentheses_ref_count = without.count('(') - without.count(')')
160
+ else
161
+ parentheses_ref_count = entry.count('(') - entry.count(')')
162
+ end
163
+
164
+ # are parentheses balanced?
165
+ if parentheses_ref_count == 0
166
+ if has_quotes
167
+ without.gsub!(/[()]/, '')
168
+ without.gsub!(/[ ]{2,}/, ' ')
169
+ #entries << (without + character_strings)
170
+ entry = (without + character_strings)
171
+ else
172
+ entry.gsub!(/[()]/, '')
173
+ entry.gsub!(/[ ]{2,}/, ' ')
174
+ entry.gsub!(/[ ]+$/, '')
175
+ #entries << entry
176
+ end
177
+ entries << entry
178
+ entry = ''
179
+ end
180
+
181
+ end
182
+
183
+ return entries
184
+ end
185
+
186
+ private
187
+
188
+ # Records sorted with more important types being at the top.
189
+ #
190
+ # @api private
191
+ def sorted_records
192
+ # pull out RRs we want to stick near the top
193
+ top_rrs = {}
194
+ top = %w{SOA NS MX SPF TXT}
195
+ top.each { |t| top_rrs[t] = @records.select { |rr| rr.type == t } }
196
+
197
+ remaining = @records.reject { |rr| top.include?(rr.type) }
198
+
199
+ # sort remaining RRs by type, alphabeticly
200
+ remaining.sort! { |a,b| a.type <=> b.type }
201
+
202
+ top_rrs.values.flatten + remaining
203
+ end
204
+
205
+
206
+ end
207
+ end