dns-zone2 0.3.2

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