File: soa.rb

package info (click to toggle)
zonecheck 3.0.3-2
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 1,456 kB
  • sloc: ruby: 6,666; xml: 693; sh: 523; python: 301; ansic: 115; makefile: 76
file content (258 lines) | stat: -rw-r--r-- 8,541 bytes parent folder | download | duplicates (3)
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# ZCTEST 1.0
# $Id: soa.rb,v 1.37 2010/06/17 09:46:17 chabannf Exp $

# 
# CONTACT     : zonecheck@nic.fr
# AUTHOR      : Stephane D'Alu <sdalu@nic.fr>
#
# CREATED     : 2002/08/02 13:58:17
# REVISION    : $Revision: 1.37 $ 
# DATE        : $Date: 2010/06/17 09:46:17 $
#
# CONTRIBUTORS: (see also CREDITS file)
#
#
# LICENSE     : GPL v3
# COPYRIGHT   : AFNIC (c) 2003
#
# This file is part of ZoneCheck.
#
# ZoneCheck is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# 
# ZoneCheck is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ZoneCheck; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#

require 'framework'

module CheckNetworkAddress
    ##
    ## Check domain SOA record
    ##
    class SOA < Test
	with_msgcat 'test/soa.%s'


	#-- Initialisation ------------------------------------------
	def initialize(*args)
	    super(*args)
	    @soa_expire_min  = const('soa:expire:min').to_i
	    @soa_expire_max  = const('soa:expire:max').to_i
	    @soa_minimum_min = const('soa:minimum:min').to_i
	    @soa_minimum_max = const('soa:minimum:max').to_i
	    @soa_refresh_min = const('soa:refresh:min').to_i
	    @soa_refresh_max = const('soa:refresh:max').to_i
	    @soa_retry_min   = const('soa:retry:min').to_i
	    @soa_retry_max   = const('soa:retry:max').to_i
	    @soa_drift_days    = const('soa:serial:drift_days').to_i
	    @soa_drift_ticks   = const('soa:serial:drift_ticks').to_i
	end

	#------------------------------------------------------------
	def is_serial_rfc1912?(serial) 
	    # Parse the serial into YYYYMMDDnn format
	    year  = serial / 1000000; serial %= 1000000
	    month = serial / 10000  ; serial %= 10000
	    day   = serial / 100
	    idx   = serial % 100

	    # If year seems too far away in the past or in the future
	    return false if (year < 1950) || (year > 2100)

	    # Check that the given date is valide
	    begin
		return [ Date::new(year, month, day), idx ]
	    rescue ArgumentError
		return false
	    end
	end

	#-- Tests ---------------------------------------------------
	# DESC: SOA entries should exists
	def chk_soa(ns, ip)
	    ! soa(ip).nil?
	end
	
	# DESC: SOA answers should be authoritative
	def chk_soa_auth(ns, ip)
	    #soa(ip, @domain.name)		# request should be done twice
      soa(ip, @domain.name, true).aa	# so we need to force the cache
	end

	# DESC: SOA email address shouldn't have an '@'
	def chk_soa_contact_sntx_at(ns, ip)
	    soa(ip).rname[0].to_s !~ /@/
	end

	# DESC: SOA email address should have a valid syntax
	def chk_soa_contact_sntx(ns, ip)
	    is_valid_mbox_address?(soa(ip).rname)
	end

	# DESC: SOA master should have a valid hostname syntax
	def chk_soa_master_sntx(ns, ip)
	    is_valid_hostname?(soa(ip).mname)
	end
	
	# DESC: SOA master should be fully qualified
	def chk_soa_master_fq(ns, ip)
	    return true if soa(ip).mname.absolute?
	    { 'mname' => soa(ip).mname.to_s }
	end

	# DESC: SOA master should not point to CNAME alias
	def chk_soa_ns_cname(ns, ip)
      return true unless name = is_cname?(soa(ip).mname, ip)
      { 'master' => soa(ip).mname.to_s, 'alias' => name.to_s }
	end
	
	# DESC: recommanded format for serial is YYYYMMDDnn
	def chk_soa_serial_fmt_YYYYMMDDnn(ns, ip)
	    serial = soa(ip).serial
	    return true if is_serial_rfc1912?(serial)
	    { 'serial' => serial.to_s }
	end

	# DESC: recommanded expire
	def chk_soa_expire(ns, ip)
	    soa_expire = soa(ip).expire
	    if (soa_expire >= @soa_expire_min) && 
		    (soa_expire <= @soa_expire_max)
	    then true
	    else { 'expire' => soa_expire.to_s }
	    end
	end

	# DESC: recommanded minimum
	def chk_soa_minimum(ns, ip)
	    soa_minimum = soa(ip).minimum
	    if (soa_minimum >= @soa_minimum_min) &&
		    (soa_minimum <= @soa_minimum_max)
	    then true
	    else { 'minimum' => soa_minimum.to_s }
	    end
	end

	# DESC: recommanded refresh
	def chk_soa_refresh(ns, ip)
	    soa_refresh = soa(ip).refresh
	    if (soa_refresh >= @soa_refresh_min) && 
		    (soa_refresh <= @soa_refresh_max)
	    then true
	    else { 'refresh' => soa_refresh.to_s }
	    end
	end

	# DESC: recommanded retry
	def chk_soa_retry(ns, ip)
	    soa_retry = soa(ip).retry
	    if (soa_retry >= @soa_retry_min) && (soa_retry <= @soa_retry_max)
	    then true
	    else { 'retry' => soa_retry.to_s }
	    end
	end

	# DESC: coherence between 'retry' and 'refresh'
	def chk_soa_retry_refresh(ns, ip)
	    return true if soa(ip).retry <= soa(ip).refresh
	    { 'retry' => soa(ip).retry.to_s, 'refresh' => soa(ip).refresh.to_s }
	end
	
	# DESC: coherence between 'expire' and 'refresh'
	def chk_soa_expire_7refresh(ns, ip)
	    return true if soa(ip).expire >= 7 * soa(ip).refresh
	    { 'expire'  => soa(ip).expire.to_s, 'refresh' => soa(ip).refresh.to_s }
	end

	# DESC: Ensure coherence between SOA and ANY
	def chk_soa_vs_any(ns, ip)
	    soa(ip) == any(ip, Dnsruby::RR::IN::SOA)[0]
	end

	# DESC: coherence of serial number with primary
	def chk_soa_coherence_serial(ns,ip)
	    begin
	      serial_ref   = (soa(@domain.ns[0][1][0]).nil?) ? nil : soa(@domain.ns[0][1][0]).serial
      rescue Dnsruby::ResolvError,Dnsruby::ResolvTimeout => e
        raise e,"#{e.to_s} on primary server (#{@domain.ns[0][0].to_s},#{@domain.ns[0][1][0]})"
      end
	    serial_other = (soa(ip).nil?) ? nil : soa(ip).serial
	    return true if serial_ref.nil? || serial_other.nil? || serial_ref >= serial_other
	    { 'serial_ref'  => serial_ref.to_s,
	      'host_ref'    => "#{@domain.ns[0][0].to_s}/#{@domain.ns[0][1][0].to_s}",
	      'serial_this' => serial_other.to_s }
	end

	# DESC: check unreasonnable drift of serial number with primary
	def chk_soa_drift_serial(ns,ip)
	    begin
	      serial_ref   = soa(@domain.ns[0][1][0]).serial
      rescue Dnsruby::ResolvError,Dnsruby::ResolvTimeout => e
        raise e,"#{e.to_s} on primary server (#{@domain.ns[0][0].to_s},#{@domain.ns[0][1][0]})"
      end
	    serial_other = soa(ip).serial
	    return true if serial_ref <= serial_other	# we don't test that
	    if d = is_serial_rfc1912?(serial_ref)
		newdate = d[0] - @soa_drift_days
		serial_min = newdate.year * 1000000 +
		    newdate.month * 10000 + newdate.day * 100
	    else
		serial_min = serial_ref -  @soa_drift_ticks
	    end
	    return true if serial_other >= serial_min # XXX: possible wrapping
	    { 'serial_min'   => serial_min.to_s,
	      'serial_ref'   => serial_ref.to_s,
	      'host_ref'     => "#{@domain.ns[0][0].to_s}/#{@domain.ns[0][1][0].to_s}",
	      'serial_this'  => serial_other.to_s }
	end

	# DESC: coherence of contact with primary
	def chk_soa_coherence_contact(ns,ip)
	    begin
	      rname_ref   = soa(@domain.ns[0][1][0]).rname
      rescue Dnsruby::ResolvError,Dnsruby::ResolvTimeout => e
        raise e,"#{e.to_s} on primary server (#{@domain.ns[0][0].to_s},#{@domain.ns[0][1][0]})"
      end
	    rname_other = soa(ip).rname
	    return true if rname_ref == rname_other
	    { 'rname_ref'   => rname_ref.to_s,
	      'host_ref'    => "#{@domain.ns[0][0].to_s}/#{@domain.ns[0][1][0].to_s}",
	      'rname_this'  => rname_other.to_s }
	end

	# DESC: coherence of master with primary
	def chk_soa_coherence_master(ns,ip)
	    begin
	      mname_ref   = soa(@domain.ns[0][1][0]).mname
      rescue Dnsruby::ResolvError,Dnsruby::ResolvTimeout => e
        raise e,"#{e.to_s} on primary server (#{@domain.ns[0][0].to_s},#{@domain.ns[0][1][0]})"
      end
	    mname_other = soa(ip).mname
	    return true if mname_ref == mname_other
	    { 'mname_ref'  => mname_ref.to_s,
	      'host_ref'   => "#{@domain.ns[0][0].to_s}/#{@domain.ns[0][1][0].to_s}",
	      'mname_this' => mname_other.to_s }
	end

	# DESC: coherence of soa with primary
	def chk_soa_coherence(ns,ip)
	    begin
	      serial_ref   = soa(@domain.ns[0][1][0]).serial
      rescue Dnsruby::ResolvError,Dnsruby::ResolvTimeout => e
        raise e,"#{e.to_s} on primary server (#{@domain.ns[0][0].to_s},#{@domain.ns[0][1][0]})"
      end
	    serial_other = soa(ip).serial
	    return true if serial_ref != serial_other
	    soa(@domain.ns[0][1][0]) == soa(ip)
	end
    end
end