File: check_soa.rb

package info (click to toggle)
dnsruby 1.73.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,548 kB
  • sloc: ruby: 17,966; makefile: 3
file content (179 lines) | stat: -rwxr-xr-x 5,075 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#! /usr/bin/env ruby

# --
# Copyright 2007 Nominet UK
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ++


# = NAME
# 
# check_soa - Check a domain's nameservers
# 
# = SYNOPSIS
# 
# check_soa domain
# 
# = DESCRIPTION
# 
# check_soa queries each of a domain's nameservers for the Start
# of Authority (SOA) record and prints the serial number.  Errors
# are printed for nameservers that couldn't be reached or didn't
# answer authoritatively.
# 
# = AUTHOR
# 
# The original Bourne Shell and C versions were printed in
# "DNS and BIND" by Paul Albitz & Cricket Liu.
# 
# The Perl version was written by Michael Fuhr <mike@fuhr.org>.
# 
# = SEE ALSO
# 
# axfr, check_zone, mresolv, mx, perldig, Net::DNS

require 'dnsruby'

NO_DOMAIN_SPECIFIED = -1
NO_NAMESERVERS      = -2


def fatal_error(message, exit_code)
  puts message
  exit(exit_code)
end


def usage
  fatal_error("Usage: #{$0} domain", NO_DOMAIN_SPECIFIED)
end


def create_resolver
  resolver = Dnsruby::Resolver.new
  resolver.retry_times = 2
  resolver.recurse = 0  # Send out non-recursive queries
  # disable caching otherwise SOA is cached from first nameserver queried
  resolver.do_caching = false
  resolver
end


def get_ns_response(resolver, domain)
  ns_response = resolver.query(domain, 'NS')
  if ns_response.header.ancount == 0
    fatal_error("No nameservers found for #{domain}.", NO_NAMESERVERS)
  end
  ns_response
end


# Finds all the nameserver domains for the domain.
def get_ns_domains(resolver, domain)
  ns_response = get_ns_response(resolver, domain)
  ns_answers = ns_response.answer.select { |r| r.type == 'NS'}
  ns_answers.map(&:domainname)
end


def process_ns_domain(resolver, domain, ns_domain)

  a_response = begin
    #  In order to lookup the IP(s) of the nameserver, we need a Resolver
    #  object that is set to our local, recursive nameserver.  So we create
    #  a new object just to do that.
    local_resolver = Dnsruby::Resolver.new

    local_resolver.query(ns_domain, 'A')
  rescue Exception => e
    puts "Cannot find address for #{ns_domain}: #{e}"
    return
  end

  a_answers = a_response.answer.select {|r| r.type == 'A'}
  a_answers.each do |a_answer|

    # ----------------------------------------------------------------------
    #  Ask this IP.
    # ----------------------------------------------------------------------
    ip_address = a_answer.address
    resolver.nameserver = ip_address.to_s
    print "#{ns_domain} (#{ip_address}): "

    # ----------------------------------------------------------------------
    #  Get the SOA record.
    # ----------------------------------------------------------------------
    soa_response = begin
      resolver.query(domain, 'SOA', 'IN')
    rescue Exception => e
      puts "Error : #{e}"
      return
    end

    # ----------------------------------------------------------------------
    #  Is this nameserver authoritative for the domain?
    # ----------------------------------------------------------------------

    unless soa_response.header.aa
      print "isn't authoritative for #{domain}\n"
      return
    end

    # ----------------------------------------------------------------------
    #  We should have received exactly one answer.
    # ----------------------------------------------------------------------

    unless soa_response.header.ancount == 1
      puts "expected 1 answer, got #{soa_response.header.ancount}."
      return
    end

    # ----------------------------------------------------------------------
    #  Did we receive an SOA record?
    # ----------------------------------------------------------------------

    answer_type = soa_response.answer[0].type
    unless answer_type == 'SOA'
      puts "expected SOA, got #{answer_type}"
      return
    end

    # ----------------------------------------------------------------------
    #  Print the serial number.
    # ----------------------------------------------------------------------

    puts "has serial number #{soa_response.answer[0].serial}"
  end
end


def main

  # Get domain from command line, printing usage and exiting if none provided:
  domain = ARGV.fetch(0) { usage }

  resolver = create_resolver

  ns_domains = get_ns_domains(resolver, domain)

  # ------------------------------------------------------------------------------
  #  Check the SOA record on each nameserver.
  # ------------------------------------------------------------------------------
  ns_domains.each do |ns_domain_name|
    process_ns_domain(resolver, domain, ns_domain_name)
  end
end


main