dns-zone 0.1.3 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7808869b41dd69a298305fa61daab900891251f
4
- data.tar.gz: 163eeab6827fd8387f2405175a9f27d0b8fb5367
3
+ metadata.gz: ce394f29889d56814fe99dee138535282dc7031f
4
+ data.tar.gz: bee58379e2fdc3d33219b7dc95c0c6a4156f557f
5
5
  SHA512:
6
- metadata.gz: 4049dc65b9e82fb08cbdc8b3e0bd4fccbc13f1ae906852da1cfb28e149b3f34bd4a717ebfc29470efa9d4daf3d2533d70fa514a9949d421810ea506bb63a4c9d
7
- data.tar.gz: 9dc5ec1a5236725d891c8be12ded6e9925c37be1960bcf2ec9f39f361dea12479c433ab0e42e95e3db07987f1851e2d26edfc53cb0381932fb0e9789f952cc80
6
+ metadata.gz: b323d369cd33d0bdc2e2ad82b1cd9d4a39681ea1f7676cc25f048f0d3e946fc3d7d45c1cb2febe1aed622f2912ca484dc17d3459541617fd8cbb1f657752e1c8
7
+ data.tar.gz: 5560b853bf4ffb3883e842c6c90b7dd2dd6c2aa4163339afe16a3e085a75594b8a5c3aaddfb2244dc6a81d0ac6a82be126944f215c802a4553f35530df5c7f57
data/HISTORY.md CHANGED
@@ -1,5 +1,24 @@
1
1
  ## HEAD
2
2
 
3
+ ## 0.2.0 (2015-10-20)
4
+
5
+ Development sponsored by Peter J. Philipp [centroid.eu](http://centroid.eu)
6
+
7
+ * Add support for DNSSEC focused RR Types:
8
+ - RFC 3403: NAPTR
9
+ - RFC 4255: SSHFP
10
+ - RFC 4034: DNSKEY, DS, RRSIG, NSEC
11
+ - RFC 7344: CDNSKEY, CDS
12
+ - RFC 4431: DLV
13
+ - RFC 5155: NSEC3, NSEC3PARAM
14
+ * Allow unqualified `domain-name` labels.
15
+ * Allow `ORIGIN` to be specified as an optional parameter when loading a zone, e.g. `zone = DNS::Zone.load(zone_as_string, 'example.com.')`
16
+
17
+ # 0.1.4 (no gem release)
18
+
19
+ * Add helper method to quickly access (or create) SOA.
20
+ * Add `dump_pretty` method to `DNS::Zone`.
21
+
3
22
  ## 0.1.3 (2014-10-21)
4
23
 
5
24
  * Fix TXT record parsing bug, when quote enclosed RDATA contained semicolons.
data/README.md CHANGED
@@ -3,8 +3,10 @@ dns-zone
3
3
 
4
4
  [![Build Status](https://secure.travis-ci.org/lantins/dns-zone.png?branch=master)](http://travis-ci.org/lantins/dns-zone)
5
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)
6
8
 
7
- A Ruby library for building and parsing DNS zone files.
9
+ A Ruby library for building, parsing and manipulating DNS zone files.
8
10
 
9
11
  ## Installation
10
12
 
@@ -26,15 +28,26 @@ Require the gem in your code:
26
28
 
27
29
  zone = DNS::Zone.load(zone_as_string)
28
30
 
31
+ ### Loading a zone file, without an `$ORIGIN` directive
32
+
33
+ zone = DNS::Zone.load(zone_as_string, "example.com.")
34
+
29
35
  ### Creating a new zone programmatically
30
36
 
31
37
  zone = DNS::Zone.new
32
38
  zone.origin = 'example.com.'
33
39
  zone.ttl = '1d'
40
+
41
+ # quick access to SOA RR
34
42
  zone.soa.nameserver = 'ns0.lividpenguin.com.'
35
43
  zone.soa.email = 'hostmaster.lividpenguin.com.'
36
-
37
- # output as dns zone file
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
38
51
  zone.dump
39
52
 
40
53
  # Development
@@ -53,6 +66,12 @@ Require the gem in your code:
53
66
  # watch for changes and run development commands (tests, documentation, etc)
54
67
  bundle exec guard
55
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
+
56
75
  # TODO
57
76
 
58
77
  ## Must have
@@ -71,6 +90,30 @@ Require the gem in your code:
71
90
  [x] Add support for RR Type: HINFO
72
91
  [x] Support loading zone where some records have an empty label
73
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
+
74
117
  ## Would be nice
75
118
 
76
119
  [ ] Handle parsing a zone file that uses more then one $ORIGIN directive.
@@ -78,7 +121,7 @@ Require the gem in your code:
78
121
  [ ] Only one SOA per zone.
79
122
  [ ] CNAMEs can't use a label of `@`.
80
123
  [ ] PTR zones have some extra conditions:
81
- [ ] labels cant be repeted
124
+ [ ] labels cant be repeated
82
125
  [ ] names should end in a dot, otherwise they are invalid after expansion
83
126
  [ ] IPv4 and IPv6 cant be mixed
84
127
 
@@ -93,17 +136,12 @@ Require the gem in your code:
93
136
  [ ] add comments to explain TTL's that are in seconds
94
137
  [ ] Ability to add comment to RR (n.b. currently we strip comments when parsing)
95
138
  [ ] Add support for RR Type: LOC (RFC 1876)
96
- [ ] Add support for RR Type: DNAME
97
- [ ] Add support for RR Type: DNSKEY
98
- [ ] Add support for RR Type: DS
139
+ [ ] Add support for RR Type: DNAME (RFC 2672)
99
140
  [ ] Add support for RR Type: KEY
100
- [ ] Add support for RR Type: NSEC
101
- [ ] Add support for RR Type: RRSIG
102
- [ ] Add support for RR Type: NAPTR
103
141
  [ ] Add support for RR Type: RP
104
142
  [ ] Add support for RR Type: RT
105
143
 
106
- # Notes
144
+ # Misc. Development Notes
107
145
 
108
146
  - RR Format: `[<TTL>] [<class>] <type> <RDATA>`
109
147
  - A DNS zone is built from RR's and a couple of other special directives.
@@ -12,7 +12,7 @@ spec = Gem::Specification.new do |s|
12
12
  s.email = ['luke@lividpenguin.com']
13
13
 
14
14
  # gem settings for what files to include.
15
- s.files = %w(Rakefile README.md HISTORY.md Gemfile Guardfile dns-zone.gemspec)
15
+ s.files = %w(Rakefile README.md HISTORY.md Gemfile dns-zone.gemspec)
16
16
  s.files += Dir.glob('{test/**/*,lib/**/*}')
17
17
  s.require_paths = ['lib']
18
18
  #s.executables = ['dns-zone']
@@ -29,9 +29,9 @@ spec = Gem::Specification.new do |s|
29
29
  s.add_development_dependency('minitest', '~> 5.0')
30
30
  s.add_development_dependency('simplecov', '~> 0.7.1')
31
31
  s.add_development_dependency('yard', '~> 0.8')
32
- s.add_development_dependency('inch', '~> 0.3')
32
+ s.add_development_dependency('inch', '~> 0.6')
33
33
  s.add_development_dependency('guard-minitest', '~> 2.0')
34
- s.add_development_dependency('guard-bundler', '~> 0')
34
+ s.add_development_dependency('guard-bundler', '~> 2.0')
35
35
 
36
36
  # long description.
37
37
  s.description = <<-EOL
@@ -9,14 +9,52 @@ module DNS
9
9
  # This is also the primary namespace for the `dns-zone` gem.
10
10
  class Zone
11
11
 
12
- attr_accessor :ttl, :origin, :records
13
-
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
14
22
  def initialize
15
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
16
50
  end
17
51
 
52
+ # Generates output of the zone and its records.
53
+ #
54
+ # @api public
18
55
  def dump
19
56
  content = []
57
+
20
58
  @records.each do |rr|
21
59
  content << rr.dump
22
60
  end
@@ -24,16 +62,26 @@ module DNS
24
62
  content.join("\n") << "\n"
25
63
  end
26
64
 
27
- # FROM RFC:
28
- # The format of these files is a sequence of entries. Entries are
29
- # predominantly line-oriented, though parentheses can be used to continue
30
- # a list of items across a line boundary, and text literals can contain
31
- # CRLF within the text. Any combination of tabs and spaces act as a
32
- # delimiter between the separate items that make up an entry. The end of
33
- # any line in the master file can end with a comment. The comment starts
34
- # with a ";" (semicolon).
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
35
80
 
36
- def self.load(string)
81
+ # Load the provided zone file data into a new DNS::Zone object.
82
+ #
83
+ # @api public
84
+ def self.load(string, default_origin = "")
37
85
  # get entries
38
86
  entries = self.extract_entries(string)
39
87
 
@@ -41,27 +89,43 @@ module DNS
41
89
 
42
90
  options = {}
43
91
  entries.each do |entry|
92
+ # read in special statments like $TTL and $ORIGIN
44
93
  if entry =~ /\$(ORIGIN|TTL)\s+(.+)/
45
94
  instance.ttl = $2 if $1 == 'TTL'
46
95
  instance.origin = $2 if $1 == 'ORIGIN'
47
96
  next
48
97
  end
49
98
 
99
+ # parse each RR and create a Ruby object for it
50
100
  if entry =~ DNS::Zone::RR::REGEX_RR
51
101
  rec = DNS::Zone::RR.load(entry, options)
52
102
  next unless rec
53
103
  instance.records << rec
54
104
  options[:last_label] = rec.label
55
105
  end
106
+ end
56
107
 
108
+ # use default_origin if we didn't see a ORIGIN directive in the zone
109
+ if instance.origin.to_s.empty? && !default_origin.empty?
110
+ instance.origin = default_origin
57
111
  end
58
112
 
59
- # read in special statments like $TTL and $ORIGIN
60
- # parse each RR and create a Ruby object for it
61
113
  return instance
62
114
  end
63
115
 
116
+ # Extract entries from a zone file that will be later parsed as RRs.
117
+ #
118
+ # @api private
64
119
  def self.extract_entries(string)
120
+ # FROM RFC:
121
+ # The format of these files is a sequence of entries. Entries are
122
+ # predominantly line-oriented, though parentheses can be used to continue
123
+ # a list of items across a line boundary, and text literals can contain
124
+ # CRLF within the text. Any combination of tabs and spaces act as a
125
+ # delimiter between the separate items that make up an entry. The end of
126
+ # any line in the master file can end with a comment. The comment starts
127
+ # with a ";" (semicolon).
128
+
65
129
  entries = []
66
130
  mode = :line
67
131
  entry = ''
@@ -115,5 +179,25 @@ module DNS
115
179
  return entries
116
180
  end
117
181
 
182
+ private
183
+
184
+ # Records sorted with more important types being at the top.
185
+ #
186
+ # @api private
187
+ def sorted_records
188
+ # pull out RRs we want to stick near the top
189
+ top_rrs = {}
190
+ top = %w{SOA NS MX SPF TXT}
191
+ top.each { |t| top_rrs[t] = @records.select { |rr| rr.type == t } }
192
+
193
+ remaining = @records.reject { |rr| top.include?(rr.type) }
194
+
195
+ # sort remaining RRs by type, alphabeticly
196
+ remaining.sort! { |a,b| a.type <=> b.type }
197
+
198
+ top_rrs.values.flatten + remaining
199
+ end
200
+
201
+
118
202
  end
119
203
  end
@@ -7,10 +7,13 @@ module DNS
7
7
 
8
8
  REGEX_TTL = /\d+[wdmhs]?/i
9
9
  REGEX_KLASS = /(?<klass>IN)?/i
10
- REGEX_TYPE = /(?<type>A|AAAA|CNAME|HINFO|MX|NS|SOA|SPF|SRV|TXT|PTR)\s{1}/i
10
+ REGEX_TYPE = /(?<type>A|AAAA|CDNSKEY|CDS|CNAME|DLV|DNSKEY|DS|HINFO|MX|NAPTR|NS|NSEC|NSEC3|NSEC3PARAM|RRSIG|SOA|SPF|SRV|SSHFP|TXT|PTR)\s{1}/i
11
11
  REGEX_RR = /^(?<label>\S+|\s{1})\s*(?<ttl>#{REGEX_TTL})?\s*#{REGEX_KLASS}\s*#{REGEX_TYPE}\s*(?<rdata>[\s\S]*)$/i
12
- REGEX_DOMAINNAME = /\S+\./i
12
+ REGEX_DOMAINNAME = /\S+\.?/i
13
13
  REGEX_STRING = /((?:[^"\\]+|\\.)*)/
14
+ REGEX_CHARACTER_STRING = %r{
15
+ "#{DNS::Zone::RR::REGEX_STRING}"|#{DNS::Zone::RR::REGEX_STRING}
16
+ }mx
14
17
 
15
18
  # Load RR string data and return an instance representing the RR.
16
19
  #
@@ -27,35 +30,57 @@ module DNS
27
30
  return nil unless captures
28
31
 
29
32
  case captures[:type]
30
- when 'A' then A.new.load(string, options)
31
- when 'AAAA' then AAAA.new.load(string, options)
32
- when 'CNAME' then CNAME.new.load(string, options)
33
- when 'HINFO' then HINFO.new.load(string, options)
34
- when 'MX' then MX.new.load(string, options)
35
- when 'NS' then NS.new.load(string, options)
36
- when 'PTR' then PTR.new.load(string, options)
37
- when 'SOA' then SOA.new.load(string, options)
38
- when 'SPF' then SPF.new.load(string, options)
39
- when 'SRV' then SRV.new.load(string, options)
40
- when 'TXT' then TXT.new.load(string, options)
33
+ when 'A' then A.new.load(string, options)
34
+ when 'AAAA' then AAAA.new.load(string, options)
35
+ when 'CDNSKEY' then CDNSKEY.new.load(string, options)
36
+ when 'CDS' then CDS.new.load(string, options)
37
+ when 'CNAME' then CNAME.new.load(string, options)
38
+ when 'DLV' then DLV.new.load(string, options)
39
+ when 'DNSKEY' then DNSKEY.new.load(string, options)
40
+ when 'DS' then DS.new.load(string, options)
41
+ when 'HINFO' then HINFO.new.load(string, options)
42
+ when 'MX' then MX.new.load(string, options)
43
+ when 'NAPTR' then NAPTR.new.load(string, options)
44
+ when 'NS' then NS.new.load(string, options)
45
+ when 'NSEC' then NSEC.new.load(string, options)
46
+ when 'NSEC3' then NSEC3.new.load(string, options)
47
+ when 'NSEC3PARAM' then NSEC3PARAM.new.load(string, options)
48
+ when 'PTR' then PTR.new.load(string, options)
49
+ when 'RRSIG' then RRSIG.new.load(string, options)
50
+ when 'SOA' then SOA.new.load(string, options)
51
+ when 'SPF' then SPF.new.load(string, options)
52
+ when 'SRV' then SRV.new.load(string, options)
53
+ when 'SSHFP' then SSHFP.new.load(string, options)
54
+ when 'TXT' then TXT.new.load(string, options)
41
55
  else
42
- raise 'Unknown RR Type'
56
+ raise 'Unknown or unsupported RR Type'
43
57
  end
44
58
  end
45
59
 
46
60
  autoload :Record, 'dns/zone/rr/record'
47
61
 
48
- autoload :A, 'dns/zone/rr/a'
49
- autoload :AAAA, 'dns/zone/rr/aaaa'
50
- autoload :CNAME, 'dns/zone/rr/cname'
51
- autoload :HINFO, 'dns/zone/rr/hinfo'
52
- autoload :MX, 'dns/zone/rr/mx'
53
- autoload :NS, 'dns/zone/rr/ns'
54
- autoload :PTR, 'dns/zone/rr/ptr'
55
- autoload :SOA, 'dns/zone/rr/soa'
56
- autoload :SPF, 'dns/zone/rr/spf'
57
- autoload :SRV, 'dns/zone/rr/srv'
58
- autoload :TXT, 'dns/zone/rr/txt'
62
+ autoload :A, 'dns/zone/rr/a'
63
+ autoload :AAAA, 'dns/zone/rr/aaaa'
64
+ autoload :CDNSKEY, 'dns/zone/rr/cdnskey'
65
+ autoload :CDS, 'dns/zone/rr/cds'
66
+ autoload :CNAME, 'dns/zone/rr/cname'
67
+ autoload :DLV, 'dns/zone/rr/dlv'
68
+ autoload :DNSKEY, 'dns/zone/rr/dnskey'
69
+ autoload :DS, 'dns/zone/rr/ds'
70
+ autoload :HINFO, 'dns/zone/rr/hinfo'
71
+ autoload :MX, 'dns/zone/rr/mx'
72
+ autoload :NAPTR, 'dns/zone/rr/naptr'
73
+ autoload :NS, 'dns/zone/rr/ns'
74
+ autoload :NSEC, 'dns/zone/rr/nsec'
75
+ autoload :NSEC3, 'dns/zone/rr/nsec3'
76
+ autoload :NSEC3PARAM, 'dns/zone/rr/nsec3param'
77
+ autoload :PTR, 'dns/zone/rr/ptr'
78
+ autoload :RRSIG, 'dns/zone/rr/rrsig'
79
+ autoload :SOA, 'dns/zone/rr/soa'
80
+ autoload :SPF, 'dns/zone/rr/spf'
81
+ autoload :SRV, 'dns/zone/rr/srv'
82
+ autoload :SSHFP, 'dns/zone/rr/sshfp'
83
+ autoload :TXT, 'dns/zone/rr/txt'
59
84
  end
60
85
 
61
86
  end
@@ -0,0 +1,5 @@
1
+ # `CDNSKEY` resource record.
2
+ #
3
+ # RFC 7344
4
+ class DNS::Zone::RR::CDNSKEY < DNS::Zone::RR::DNSKEY
5
+ end
@@ -0,0 +1,5 @@
1
+ # `CDS` resource record.
2
+ #
3
+ # RFC 7344
4
+ class DNS::Zone::RR::CDS < DNS::Zone::RR::DS
5
+ end
@@ -0,0 +1,5 @@
1
+ # `DLV` resource record.
2
+ #
3
+ # RFC 4431
4
+ class DNS::Zone::RR::DLV < DNS::Zone::RR::DS
5
+ end