dns-zone 0.1.3 → 0.2.0

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