diff --git a/app/models/ptr.rb b/app/models/ptr.rb new file mode 100644 index 0000000..daa4351 --- /dev/null +++ b/app/models/ptr.rb @@ -0,0 +1,4 @@ +class PTR < Record + validates :content, presence: true, hostname: true +end + diff --git a/app/models/record.rb b/app/models/record.rb index 46b6829..bf96011 100644 --- a/app/models/record.rb +++ b/app/models/record.rb @@ -1,58 +1,71 @@ +require 'ipaddr' + class Record < ActiveRecord::Base belongs_to :domain def self.record_types [ 'SOA', 'NS', 'CNAME', 'A', 'AAAA', 'MX', 'TXT', 'SPF', 'SRV', 'SSHFP', 'PTR', ] end validates :name, presence: true validates :type, inclusion: { in: record_types } + before_validation :guess_reverse_name before_validation :set_name after_save :update_zone_serial after_destroy :update_zone_serial def short return '' if name == domain.name return '' if name.blank? File.basename(name, ".#{domain.name}") end def supports_prio? false end # Create record specific urls for all record types # # Overrides default rails STI def self.model_name return super if self == Record Record.model_name end private # Hooks + def guess_reverse_name + return if not type == 'PTR' + return if not domain.reverse? + return if name.blank? + + reverse = IPAddr.new(name).reverse + self.name = reverse if reverse.end_with?(domain.name) + rescue IPAddr::InvalidAddressError # rubycop:disable HandleExceptions + end + # Powerdns expects full domain names def set_name self.name = domain.name if name.blank? self.name = "#{name}.#{domain.name}" if not name.end_with?(domain.name) end def update_zone_serial # SOA records handle serial themselves return true if type == 'SOA' domain.soa.bump_serial! end end diff --git a/test/factories/ptr.rb b/test/factories/ptr.rb new file mode 100644 index 0000000..1b63cdc --- /dev/null +++ b/test/factories/ptr.rb @@ -0,0 +1,23 @@ +FactoryGirl.define do + sequence(:byte) { |n| (n % 256).to_s } + sequence(:nibble) { |n| (n % 16).to_s(16) } + + factory :v4_ptr, class: 'PTR' do + domain factory: :v4_arpa_domain + name { generate(:byte) } + content { generate(:domain) } + end + + factory :v6_ptr, class: 'PTR' do + domain factory: :v6_arpa_domain + name { + all_nibbles = '0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'.split('.').size + domain_nibbles = domain.name.split('.').size + missing = [] + (all_nibbles - domain_nibbles).times { missing << generate(:nibble) } + + missing.join('.') + } + content { generate(:domain) } + end +end diff --git a/test/models/ptr_test.rb b/test/models/ptr_test.rb new file mode 100644 index 0000000..7ba80e9 --- /dev/null +++ b/test/models/ptr_test.rb @@ -0,0 +1,42 @@ +require 'test_helper' + +class PTRTest < ActiveSupport::TestCase + class V4PTRTest < ActiveSupport::TestCase + setup do + @record = build(:v4_ptr) + end + + test 'save' do + @record.save + + assert_empty @record.errors + end + + test 'guess record' do + @record.name = '192.0.2.1' + assert @record.save + + assert_equal '1.2.0.192.in-addr.arpa', @record.name + end + end + + class V6PTRTest < ActiveSupport::TestCase + setup do + @record = build(:v6_ptr) + end + + test 'save' do + @record.save + assert_empty @record.errors + end + + test 'guess record' do + @record.name = '2001:db8::2' + assert @record.save + + assert_equal '2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa', + @record.name + end + end + +end