From 34410a8ed5c199c0f11cabce3a3f5b54a4a8d486 Mon Sep 17 00:00:00 2001 From: Martin Lensment Date: Tue, 11 Nov 2014 17:00:50 +0200 Subject: [PATCH] Basic zonefile generating --- app/controllers/admin/domains_controller.rb | 7 +- app/models/domain.rb | 29 ++++ app/views/admin/domains/zonefile.haml | 2 + config/application.rb | 2 + config/routes.rb | 6 +- lib/zonefile.rb | 148 ++++++++++++++++++++ 6 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 app/views/admin/domains/zonefile.haml create mode 100644 lib/zonefile.rb diff --git a/app/controllers/admin/domains_controller.rb b/app/controllers/admin/domains_controller.rb index a5f50447e..c12492edb 100644 --- a/app/controllers/admin/domains_controller.rb +++ b/app/controllers/admin/domains_controller.rb @@ -1,5 +1,5 @@ class Admin::DomainsController < AdminController - before_action :set_domain, only: [:show, :edit, :update] + before_action :set_domain, only: [:show, :edit, :update, :zonefile] def index @q = Domain.includes(:registrar, :owner_contact).search(params[:q]) @@ -27,6 +27,11 @@ class Admin::DomainsController < AdminController end end + def zonefile + @zonefile = @domain.generate_zonefile + # send_data @zonefile, filename: 'bla.txt' + end + private def set_domain diff --git a/app/models/domain.rb b/app/models/domain.rb index 4e3f6a937..621ba123b 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -298,6 +298,35 @@ class Domain < ActiveRecord::Base end end + def generate_zonefile + zf = Zonefile.new + zf.ttl = '3600' + zf.origin = "#{name}." + ns = nameservers.first + zf.soa[:primary_ns] = "#{ns.hostname}." + zf.soa[:email] = 'hostmaster.internet.ee' + zf.soa[:origin] = "#{name}." + zf.soa[:refresh] = '10800' + zf.soa[:retry] = '3600' + zf.soa[:expire] = '604800' + zf.soa[:minimumTTL] = '3600' + + nameservers.each do |x| + zf.ns << { name: "#{name}.", class: 'IN', host: "#{x.hostname}." } + end + + dnskeys.each do |x| + zf.ds << { name: "#{name}.", ttl: '86400', class: 'IN', key_tag: x.ds_key_tag, algorithm: x.ds_alg, + digest_type: x.ds_digest_type, digest: x.ds_digest } + + zf.dnskey << { name: "#{name}.", ttl: '86400', class: 'IN', flag: x.flags, + protocol: x.protocol, algorithm: x.alg, public_key: x.public_key } + end + + zf.new_serial + zf.generate + end + class << self def convert_period_to_time(period, unit) return period.to_i.days if unit == 'd' diff --git a/app/views/admin/domains/zonefile.haml b/app/views/admin/domains/zonefile.haml new file mode 100644 index 000000000..3c5b588ee --- /dev/null +++ b/app/views/admin/domains/zonefile.haml @@ -0,0 +1,2 @@ += preserve do + %pre= @zonefile diff --git a/config/application.rb b/config/application.rb index 8e4a6a7c2..f119c2ebd 100644 --- a/config/application.rb +++ b/config/application.rb @@ -27,6 +27,8 @@ module Registry # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + config.autoload_paths += %W(#{config.root}/lib) + config.generators do |g| g.stylesheets false g.javascripts false diff --git a/config/routes.rb b/config/routes.rb index a3aacba3c..b381b856d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,7 +7,11 @@ Rails.application.routes.draw do ## ADMIN ROUTES namespace(:admin) do - resources :domains + resources :domains do + member do + get 'zonefile' + end + end resources :settings resources :registrars do collection do diff --git a/lib/zonefile.rb b/lib/zonefile.rb new file mode 100644 index 000000000..d1d7cc7d5 --- /dev/null +++ b/lib/zonefile.rb @@ -0,0 +1,148 @@ +class Zonefile + + RECORDS = [:mx, :a, :a4, :ns, :cname, :txt, :ptr, :srv, :soa, :ds, + :dnskey, :rrsig, :nsec,:nsec3, :nsec3param, :tlsa, :naptr] + + attr_accessor(*RECORDS, :ttl, :origin) + + def initialize(obj = {}) + RECORDS.each do |x| + if x == :soa + send("#{x}=", {}) + else + send("#{x}=", []) + end + end + + obj.each do |k, v| + send("#{k}=", v) + end + end + + def new_serial + base = "%04d%02d%02d" % [Time.now.year, Time.now.month, Time.now.day] + + if (@soa[:serial].to_i / 100) > base.to_i + ns = @soa[:serial].to_i + 1 + @soa[:serial] = ns.to_s + return ns.to_s + end + + ii = 0 + while (("#{base}%02d" % ii).to_i <= @soa[:serial].to_i) do + ii += 1 + end + @soa[:serial] = "#{base}%02d" % ii + end + + def generate + out = <<-eos +$ORIGIN #{origin} ; designates the start of this zone file in the namespace +$TTL #{ttl} ; default expiration time of all resource records without their own TTL value + +#{self.soa[:origin]} #{self.soa[:ttl]} IN SOA #{self.soa[:primary_ns]} #{self.soa[:email]} ( + #{sprintf('%-13s', soa[:serial])}; serial number + #{sprintf('%-13s', soa[:refresh])}; refresh, seconds + #{sprintf('%-13s', soa[:retry])}; retry, seconds + #{sprintf('%-13s', soa[:expire])}; expire, seconds + #{sprintf('%-13s', soa[:minimumTTL])}; minimum TTL, seconds + ) + eos + + self.ns.each do |ns| + out << "#{ns[:name]} #{ns[:ttl]} #{ns[:class]} NS #{ns[:host]}\n" + end + + out << "\n; Zone MX Records\n" unless self.mx.empty? + + self.mx.each do |mx| + out << "#{mx[:name]} #{mx[:ttl]} #{mx[:class]} MX #{mx[:pri]} #{mx[:host]}\n" + end + + out << "\n; Zone A Records\n" unless self.a.empty? + + self.a.each do |a| + out << "#{a[:name]} #{a[:ttl]} #{a[:class]} A #{a[:host]}\n" + end + + out << "\n; Zone CNAME Records\n" unless self.cname.empty? + + self.cname.each do |cn| + out << "#{cn[:name]} #{cn[:ttl]} #{cn[:class]} CNAME #{cn[:host]}\n" + end + + out << "\n; Zone AAAA Records\n" unless self.a4.empty? + + self.a4.each do |a4| + out << "#{a4[:name]} #{a4[:ttl]} #{a4[:class]} AAAA #{a4[:host]}\n" + end + + out << "\n; Zone TXT Records\n" unless self.txt.empty? + + self.txt.each do |tx| + out << "#{tx[:name]} #{tx[:ttl]} #{tx[:class]} TXT #{tx[:text]}\n" + end + + out << "\n; Zone SRV Records\n" unless self.srv.empty? + + self.srv.each do |srv| + out << "#{srv[:name]} #{srv[:ttl]} #{srv[:class]} SRV #{srv[:pri]} #{srv[:weight]} #{srv[:port]} #{srv[:host]}\n" + end + + out << "\n; Zone PTR Records\n" unless self.ptr.empty? + + self.ptr.each do |ptr| + out << "#{ptr[:name]} #{ptr[:ttl]} #{ptr[:class]} PTR #{ptr[:host]}\n" + end + + out << "\n; Zone DS Records\n" unless self.ds.empty? + + self.ds.each do |ds| + out << "#{ds[:name]} #{ds[:ttl]} #{ds[:class]} DS #{ds[:key_tag]} #{ds[:algorithm]} #{ds[:digest_type]} #{ds[:digest]}\n" + end + + out << "\n; Zone NSEC Records\n" unless self.ds.empty? + + self.nsec.each do |nsec| + out << "#{nsec[:name]} #{nsec[:ttl]} #{nsec[:class]} NSEC #{nsec[:next]} #{nsec[:types]}\n" + end + + out << "\n; Zone NSEC3 Records\n" unless self.ds.empty? + + self.nsec3.each do |nsec3| + out << "#{nsec3[:name]} #{nsec3[:ttl]} #{nsec3[:class]} NSEC3 #{nsec3[:algorithm]} #{nsec3[:flags]} #{nsec3[:iterations]} #{nsec3[:salt]} #{nsec3[:next]} #{nsec3[:types]}\n" + end + + out << "\n; Zone NSEC3PARAM Records\n" unless self.ds.empty? + + self.nsec3param.each do |nsec3param| + out << "#{nsec3param[:name]} #{nsec3param[:ttl]} #{nsec3param[:class]} NSEC3PARAM #{nsec3param[:algorithm]} #{nsec3param[:flags]} #{nsec3param[:iterations]} #{nsec3param[:salt]}\n" + end + + out << "\n; Zone DNSKEY Records\n" unless self.ds.empty? + + self.dnskey.each do |dnskey| + out << "#{dnskey[:name]} #{dnskey[:ttl]} #{dnskey[:class]} DNSKEY #{dnskey[:flag]} #{dnskey[:protocol]} #{dnskey[:algorithm]} #{dnskey[:public_key]}\n" + end + + out << "\n; Zone RRSIG Records\n" unless self.ds.empty? + + self.rrsig.each do |rrsig| + out << "#{rrsig[:name]} #{rrsig[:ttl]} #{rrsig[:class]} RRSIG #{rrsig[:type_covered]} #{rrsig[:algorithm]} #{rrsig[:labels]} #{rrsig[:original_ttl]} #{rrsig[:expiration]} #{rrsig[:inception]} #{rrsig[:key_tag]} #{rrsig[:signer]} #{rrsig[:signature]}\n" + end + + out << "\n; Zone TLSA Records\n" unless self.tlsa.empty? + + self.tlsa.each do |tlsa| + out << "#{tlsa[:name]} #{tlsa[:ttl]} #{tlsa[:class]} TLSA #{tlsa[:certificate_usage]} #{tlsa[:selector]} #{tlsa[:matching_type]} #{tlsa[:data]}\n" + end + + out << "\n; Zone NAPTR Records\n" unless self.ds.empty? + + self.naptr.each do |naptr| + out << "#{naptr[:name]} #{naptr[:ttl]} #{naptr[:class]} NAPTR #{naptr[:order]} #{naptr[:preference]} #{naptr[:flags]} #{naptr[:service]} #{naptr[:regexp]} #{naptr[:replacement]}\n" + end + + out + end +end