wlvalidate 0.1.6 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/wlvalidate.rb +163 -48
- metadata +20 -4
data/lib/wlvalidate.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
Results of the below calls will be returned in the following format
|
4
4
|
(TYPE, RESULT, DATA and EXT_DATA are return values in each hash line)
|
5
5
|
+=======+===========+===================+===========++========+=================+======================+
|
6
|
-
[
|
6
|
+
[ CALL | RESULT | DATA | EXT_DATA ][ Status | Return Type | Return on all paths? ]
|
7
7
|
+=======+===========+===================+===========++========+=================+======================+
|
8
8
|
| A | PASS/FAIL | (none) | (none) || Done | Array of hashes | Yes |
|
9
9
|
| SPF | PASS/FAIL | SPF record | Fail type || Done | Hash | Yes |
|
@@ -15,10 +15,6 @@
|
|
15
15
|
| ALL | (multi) | (multi) | (multi) || Done | Array of hashes | (n/a) |
|
16
16
|
+-------+-----------+-------------------+-----------++--------+-----------------+----------------------+
|
17
17
|
|
18
|
-
TO DO:
|
19
|
-
- Add master/slave DNS option
|
20
|
-
- Validate hashes for Ruby
|
21
|
-
|
22
18
|
=end
|
23
19
|
|
24
20
|
# Must have dnsruby gem installed for this to work
|
@@ -27,15 +23,84 @@ include Dnsruby
|
|
27
23
|
|
28
24
|
module WLValidate
|
29
25
|
class Validate
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
@@dns_updated = 0
|
27
|
+
@@dns_server = "8.8.8.8"
|
28
|
+
@@default_dns_server = "8.8.4.4"
|
29
|
+
@@dns_type = 1
|
30
|
+
|
31
|
+
def initialize( dns_server="8.8.4.4", dns_type=1 )
|
32
|
+
# PURPOSE: Executes code before anything else runs (hence Initialize)
|
33
|
+
# RETURNS: Not a darn thing...
|
34
|
+
# NOTES: We want to soak in parameters for DNS servers (array), DNS type (integer) and DNS timeouts (integer):
|
35
|
+
# Resulting global variables: @dns_servers, @dns_type, @dns_timeout
|
36
|
+
# If these values are not set during instancing, we want to put in some default values
|
37
|
+
#
|
38
|
+
# VARS: @dns_server: single string, non-array of IP address
|
39
|
+
# @dns_type: integer where 1 = local config, 2 = authorative server, 3 = Specific server
|
40
|
+
|
41
|
+
@dns_updated = 0
|
42
|
+
@dns_server = dns_server.to_s
|
43
|
+
@default_dns_server = "8.8.4.4"
|
44
|
+
@dns_type = dns_type.to_i
|
45
|
+
|
46
|
+
if (dns_type.to_i == 3)
|
47
|
+
if (dns_server)
|
48
|
+
@dns_server = dns_server
|
49
|
+
else
|
50
|
+
@dns_server = @default_dns_server
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_info()
|
56
|
+
hashReturn = Hash.new
|
57
|
+
hashReturn = { "dns_server" => @dns_server, "dns_type" => @dns_type, "dns_timeout" => @dns_timeout, "dns_updated" => @dns_updated }
|
58
|
+
return hashReturn
|
33
59
|
end
|
34
60
|
|
35
|
-
def
|
61
|
+
def set_auth_ns(strDomain)
|
62
|
+
# Since we can't get strDomain during __init__, we will check to see if @dns_type = 2 (auth) and if
|
63
|
+
# @dns_server is empty. If so, then we want to get the authorative NS and set it globally.
|
64
|
+
if ((@dns_updated == 0) && (@dns_type == 2))
|
65
|
+
iplist = []
|
66
|
+
begin
|
67
|
+
res = Dnsruby::Resolver.new
|
68
|
+
|
69
|
+
res.retry_times=(1)
|
70
|
+
ns_req = nil
|
71
|
+
ns_req = res.query(strDomain, 'NS')
|
72
|
+
|
73
|
+
res.recurse=(0)
|
74
|
+
|
75
|
+
(ns_req.answer.select {|r| r.type == 'NS'}).each do |nsrr|
|
76
|
+
ns = nsrr.domainname
|
77
|
+
|
78
|
+
local_res = Dnsruby::Resolver.new
|
79
|
+
a_req=nil
|
80
|
+
a_req = local_res.query(ns, 'A')
|
81
|
+
|
82
|
+
(a_req.answer.select {|r| r.type == 'A'}).each do |r|
|
83
|
+
ip = r.address
|
84
|
+
res.nameserver=(ip.to_s)
|
85
|
+
soa_req=nil
|
86
|
+
soa_req = res.query(strDomain, 'SOA', 'IN')
|
87
|
+
iplist.push(ip.to_s)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
iplist.shuffle!
|
91
|
+
@dns_server = iplist.first
|
92
|
+
@dns_updated = 1
|
93
|
+
rescue
|
94
|
+
@dns_server = @default_dns_server
|
95
|
+
@dns_updated = 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def ALL(strWL, strDomain)
|
36
101
|
# PURPOSE: Execute all of the below methods as a single call
|
37
102
|
# RETURNS: Returns the combined array of hashes as returned by each function
|
38
|
-
# NOTES:
|
103
|
+
# NOTES: All functions have values on all return paths, except for MX (see TODO above)
|
39
104
|
|
40
105
|
arrayReturns = Array.new
|
41
106
|
|
@@ -45,6 +110,7 @@ module WLValidate
|
|
45
110
|
hashDKIM1 = self.DKIM1(strWL, strDomain)
|
46
111
|
hashDKIM2 = self.DKIM2(strWL, strDomain)
|
47
112
|
arrayMX = self.MX(strWL, strDomain)
|
113
|
+
hashInfo = self.get_info()
|
48
114
|
|
49
115
|
# array.push hashes, and array.concat arrays
|
50
116
|
arrayReturns.push(hashSPF)
|
@@ -53,11 +119,12 @@ module WLValidate
|
|
53
119
|
arrayReturns.push(hashDKIM1)
|
54
120
|
arrayReturns.push(hashDKIM2)
|
55
121
|
arrayReturns.concat(arrayMX)
|
122
|
+
arrayReturns.push(hashInfo)
|
56
123
|
|
57
124
|
return arrayReturns
|
58
125
|
end
|
59
126
|
|
60
|
-
def
|
127
|
+
def MX(strWL, strDomain)
|
61
128
|
# PURPOSE: Loop through all <domain> MX records
|
62
129
|
# RETURNS: Array of hashes
|
63
130
|
hashTemp = Hash.new
|
@@ -66,9 +133,16 @@ module WLValidate
|
|
66
133
|
|
67
134
|
if ((strWL == "") || (strDomain == ""))
|
68
135
|
hashReturn = { "type" => "MX", "result" => "ERROR", "data" => "Missing parameters", "ext_data" => "One or more parameters are missing!" }
|
136
|
+
arrayReturn.push(hashReturn)
|
137
|
+
end
|
138
|
+
|
139
|
+
set_auth_ns(strDomain)
|
140
|
+
|
141
|
+
if ((@dns_type == 2) || (@dns_type == 3))
|
142
|
+
dns_params = {:nameserver=>@dns_server}
|
69
143
|
end
|
70
144
|
|
71
|
-
Dnsruby::DNS.open {|dns|
|
145
|
+
Dnsruby::DNS.open(dns_params) {|dns|
|
72
146
|
begin
|
73
147
|
arrayMX = dns.getresources(strDomain, Dnsruby::Types.MX)
|
74
148
|
if (arrayMX.count > 0)
|
@@ -76,7 +150,11 @@ module WLValidate
|
|
76
150
|
arrayMX.each do |r|
|
77
151
|
myExchange = r.exchange.to_s
|
78
152
|
myPreference = r.preference.to_s
|
79
|
-
|
153
|
+
if myExchange.match %r{sendgrid.}
|
154
|
+
hashTemp = { "type" => "MX", "result" => "ERROR", "data" => myExchange, "ext_data" => "SendGrid is listed as handling incoming email on the root domain - this is generally a bad idea unless you're using ParseAPI..." }
|
155
|
+
else
|
156
|
+
hashTemp = { "type" => "MX", "result" => "INFO", "data" => myExchange, "ext_data" => myPreference }
|
157
|
+
end
|
80
158
|
arrayReturn.push(hashTemp)
|
81
159
|
hashTemp = {}
|
82
160
|
end
|
@@ -99,7 +177,7 @@ module WLValidate
|
|
99
177
|
return arrayReturn
|
100
178
|
end
|
101
179
|
|
102
|
-
def
|
180
|
+
def A(strWL, strDomain)
|
103
181
|
# PURPOSE: Loop through all oN.<wl>.<domain>.<tlds> to get IPs, then also do reverse DNS checks
|
104
182
|
# RETURNS: Hash - see above for definition
|
105
183
|
|
@@ -108,8 +186,11 @@ module WLValidate
|
|
108
186
|
|
109
187
|
if ((strWL == "") || (strDomain == ""))
|
110
188
|
hashReturn = { "type" => "A", "result" => "ERROR", "data" => "Missing parameters", "ext_data" => "One or more expected paramters are missing!" }
|
189
|
+
arrayReturn.push(hashReturn)
|
111
190
|
end
|
112
191
|
|
192
|
+
set_auth_ns(strDomain)
|
193
|
+
|
113
194
|
i = 1
|
114
195
|
intDNSIPLoops = 11
|
115
196
|
while i < intDNSIPLoops do
|
@@ -142,7 +223,7 @@ module WLValidate
|
|
142
223
|
return arrayReturn
|
143
224
|
end
|
144
225
|
|
145
|
-
def
|
226
|
+
def CNAME(strWL, strDomain)
|
146
227
|
# PURPOSE: Get CNAME record and make sure it's mapped to 'sendgrid.net'
|
147
228
|
# RETURNS: Hash - See above for definition
|
148
229
|
|
@@ -153,7 +234,13 @@ module WLValidate
|
|
153
234
|
hashReturn = { "type" => "CNAME", "result" => "ERROR", "data" => "Missing parameters", "ext_data" => "One or more expected paramters are missing!" }
|
154
235
|
end
|
155
236
|
|
156
|
-
|
237
|
+
set_auth_ns(strDomain)
|
238
|
+
|
239
|
+
if ((@dns_type == 2) || (@dns_type == 3))
|
240
|
+
dns_params = {:nameserver=>@dns_server}
|
241
|
+
end
|
242
|
+
|
243
|
+
Dnsruby::DNS.open(dns_params) {|dns|
|
157
244
|
begin
|
158
245
|
strHost = strWL + "." + strDomain
|
159
246
|
arrayCNAME = dns.getresources(strHost, Dnsruby::Types.CNAME)
|
@@ -166,9 +253,12 @@ module WLValidate
|
|
166
253
|
end
|
167
254
|
end
|
168
255
|
if hashReturn.empty?
|
169
|
-
hashReturn = { "type" => "CNAME", "result" => "FAIL", "data" =>
|
256
|
+
hashReturn = { "type" => "CNAME", "result" => "FAIL", "data" => strHost, "ext_data" => "No matching CNAME records found" }
|
170
257
|
end
|
171
258
|
end
|
259
|
+
if boolFoundMatch == false
|
260
|
+
hashReturn = { "type" => "CNAME", "result" => "FAIL", "data" => strHost, "ext_data" => "No matching CNAME records found" }
|
261
|
+
end
|
172
262
|
rescue Dnsruby::ResolvTimeout
|
173
263
|
hashReturn = { "type" => "CNAME", "result" => "ERROR", "data" => strHost, "ext_data" => "Timed out while attempting rDNS lookup" }
|
174
264
|
rescue
|
@@ -178,7 +268,7 @@ module WLValidate
|
|
178
268
|
return hashReturn
|
179
269
|
end
|
180
270
|
|
181
|
-
def
|
271
|
+
def DKIM1(strWL, strDomain)
|
182
272
|
# PURPOSE: Get TXT/CNAME record for smtpapi._domainkey.<domain>.<tlds>
|
183
273
|
# RETURNS: Hash - See above for definition
|
184
274
|
|
@@ -186,32 +276,41 @@ module WLValidate
|
|
186
276
|
boolFoundMatch = false
|
187
277
|
strHost = "smtpapi._domainkey." + strDomain
|
188
278
|
strDKIM = "k=rsa; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPtW5iwpXVPiH5FzJ7Nrl8USzuY9zqqzjE0D1r04xDN6qwziDnmgcFNNfMewVKN2D1O+2J9N14hRprzByFwfQW76yojh54Xu3uSbQ3JP0A7k8o8GutRF8zbFUA8n0ZH2y0cIEjMliXY4W4LwPA7m4q0ObmvSjhd63O9d8z1XkUBwIDAQAB"
|
189
|
-
strDKIM_k = "rsa"
|
190
|
-
strDKIM_t = "s"
|
191
|
-
strDKIM_p = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPtW5iwpXVPiH5FzJ7Nrl8USzuY9zqqzjE0D1r04xDN6qwziDnmgcFNNfMewVKN2D1O+2J9N14hRprzByFwfQW76yojh54Xu3uSbQ3JP0A7k8o8GutRF8zbFUA8n0ZH2y0cIEjMliXY4W4LwPA7m4q0ObmvSjhd63O9d8z1XkUBwIDAQAB"
|
192
279
|
|
193
280
|
if ((strWL == "") || (strDomain == ""))
|
194
281
|
hashReturn = { "type" => "DKIM1", "result" => "ERROR", "data" => "Missing parameters", "ext_data" => "One or more expected paramters are missing!" }
|
195
282
|
end
|
196
283
|
|
197
|
-
|
284
|
+
set_auth_ns(strDomain)
|
285
|
+
|
286
|
+
if ((@dns_type == 2) || (@dns_type == 3))
|
287
|
+
dns_params = {:nameserver=>@dns_server}
|
288
|
+
end
|
289
|
+
|
290
|
+
Dnsruby::DNS.open(dns_params) {|dns|
|
198
291
|
begin
|
199
292
|
arrayDKIM1 = dns.getresources(strHost, Dnsruby::Types.TXT)
|
200
293
|
arrayDKIM1.each do |r|
|
201
|
-
if
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
294
|
+
if r.name.to_s.match %r{dkim.sendgrid.net}
|
295
|
+
hashReturn = { "type" => "DKIM1", "result" => "PASS", "data" => r.name.to_s, "ext_data" => "CNAME" }
|
296
|
+
boolFoundMatch = true
|
297
|
+
end
|
298
|
+
if ((r.name.to_s == strHost) && (r.data == strDKIM))
|
299
|
+
hashReturn = { "type" => "DKIM1", "result" => "PASS", "data" => r.name.to_s, "ext_data" => "TXT" }
|
300
|
+
boolFoundMatch = true
|
301
|
+
end
|
302
|
+
end
|
303
|
+
if ((boolFoundMatch == false) || (hashReturn.empty?))
|
304
|
+
arrayDKIM1CNAME = dns.getresources(strHost, Dnsruby::Types.CNAME)
|
305
|
+
arrayDKIM1CNAME.each do |c|
|
306
|
+
if c.rdata_to_string.match %r{dkim.sendgrid.net}
|
307
|
+
hashReturn = { "type" => "DKIM1", "result" => "PASS", "data" => c.name.to_s, "ext_data" => "CNAME" }
|
207
308
|
boolFoundMatch = true
|
208
|
-
else
|
209
|
-
hashReturn = { "type" => "DKIM1", "result" => "FAIL", "data" => strHost, "ext_data" => "No DKIM record found as either CNAME or TXT" }
|
210
309
|
end
|
211
310
|
end
|
212
311
|
end
|
213
|
-
if hashReturn.empty?
|
214
|
-
hashReturn = { "type" => "
|
312
|
+
if ((boolFoundMatch == false) || (hashReturn.empty?))
|
313
|
+
hashReturn = { "type" => "DKIM1", "result" => "FAIL", "data" => strHost, "ext_data" => "No matching DKIM/TXT records found" }
|
215
314
|
end
|
216
315
|
rescue
|
217
316
|
hashReturn = { "type" => "DKIM1", "result" => "ERROR", "data" => strHost, "ext_data" => "Unable to resolve host, or query timed out" }
|
@@ -220,7 +319,7 @@ module WLValidate
|
|
220
319
|
return hashReturn
|
221
320
|
end
|
222
321
|
|
223
|
-
def
|
322
|
+
def DKIM2(strWL, strDomain)
|
224
323
|
# PURPOSE: Get TXT/CNAME record for smtpapi._domainkey.<wl>.<domain>.<tlds>
|
225
324
|
# RETURNS: Hash - See above for definition
|
226
325
|
|
@@ -228,31 +327,41 @@ module WLValidate
|
|
228
327
|
boolFoundMatch = false
|
229
328
|
strHost = "smtpapi._domainkey." + strWL + "." + strDomain
|
230
329
|
strDKIM = "k=rsa; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPtW5iwpXVPiH5FzJ7Nrl8USzuY9zqqzjE0D1r04xDN6qwziDnmgcFNNfMewVKN2D1O+2J9N14hRprzByFwfQW76yojh54Xu3uSbQ3JP0A7k8o8GutRF8zbFUA8n0ZH2y0cIEjMliXY4W4LwPA7m4q0ObmvSjhd63O9d8z1XkUBwIDAQAB"
|
231
|
-
strDKIM_k = "rsa"
|
232
|
-
strDKIM_t = "s"
|
233
|
-
strDKIM_p = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPtW5iwpXVPiH5FzJ7Nrl8USzuY9zqqzjE0D1r04xDN6qwziDnmgcFNNfMewVKN2D1O+2J9N14hRprzByFwfQW76yojh54Xu3uSbQ3JP0A7k8o8GutRF8zbFUA8n0ZH2y0cIEjMliXY4W4LwPA7m4q0ObmvSjhd63O9d8z1XkUBwIDAQAB"
|
234
330
|
|
235
331
|
if ((strWL == "") || (strDomain == ""))
|
236
332
|
hashReturn = { "type" => "DKIM2", "result" => "ERROR", "data" => "Missing parameters", "ext_data" => "One or more expected paramters are missing!" }
|
237
333
|
end
|
238
334
|
|
239
|
-
|
335
|
+
set_auth_ns(strDomain)
|
336
|
+
|
337
|
+
if ((@dns_type == 2) || (@dns_type == 3))
|
338
|
+
dns_params = {:nameserver=>@dns_server}
|
339
|
+
end
|
340
|
+
|
341
|
+
Dnsruby::DNS.open(dns_params) {|dns|
|
240
342
|
begin
|
241
343
|
arrayDKIM2 = dns.getresources(strHost, Dnsruby::Types.TXT)
|
242
344
|
arrayDKIM2.each do |r|
|
243
|
-
if
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
345
|
+
if r.name.to_s.match %r{dkim.sendgrid.net}
|
346
|
+
hashReturn = { "type" => "DKIM2", "result" => "PASS", "data" => r.name.to_s, "ext_data" => "CNAME" }
|
347
|
+
boolFoundMatch = true
|
348
|
+
end
|
349
|
+
if ((r.name.to_s == strHost) && (r.data == strDKIM))
|
350
|
+
hashReturn = { "type" => "DKIM2", "result" => "PASS", "data" => r.name.to_s, "ext_data" => "TXT" }
|
351
|
+
boolFoundMatch = true
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
if ((boolFoundMatch == false) || (hashReturn.empty?))
|
356
|
+
arrayDKIM2CNAME = dns.getresources(strHost, Dnsruby::Types.CNAME)
|
357
|
+
arrayDKIM2CNAME.each do |c|
|
358
|
+
if c.rdata_to_string.match %r{dkim.sendgrid.net}
|
359
|
+
hashReturn = { "type" => "DKIM2", "result" => "PASS", "data" => c.name.to_s, "ext_data" => "CNAME" }
|
249
360
|
boolFoundMatch = true
|
250
|
-
else
|
251
|
-
hashReturn = { "type" => "DKIM2", "result" => "FAIL", "data" => strHost, "ext_data" => "No DKIM record found as either CNAME or TXT" }
|
252
361
|
end
|
253
362
|
end
|
254
363
|
end
|
255
|
-
if hashReturn.empty?
|
364
|
+
if ((boolFoundMatch == false) || (hashReturn.empty?))
|
256
365
|
hashReturn = { "type" => "DKIM2", "result" => "FAIL", "data" => strHost, "ext_data" => "No matching DKIM/TXT records found" }
|
257
366
|
end
|
258
367
|
rescue
|
@@ -262,7 +371,7 @@ module WLValidate
|
|
262
371
|
return hashReturn
|
263
372
|
end
|
264
373
|
|
265
|
-
def
|
374
|
+
def SPF(strWL, strDomain)
|
266
375
|
# PURPOSE: Get TXT record and make sure it has include:sendgrid.net
|
267
376
|
# RETURNS: Hash - See above for definition
|
268
377
|
|
@@ -273,7 +382,13 @@ module WLValidate
|
|
273
382
|
hashReturn = { "type" => "SPF", "result" => "ERROR", "data" => "Missing parameters", "ext_data" => "One or more expected paramters are missing!" }
|
274
383
|
end
|
275
384
|
|
276
|
-
|
385
|
+
set_auth_ns(strDomain)
|
386
|
+
|
387
|
+
if ((@dns_type == 2) || (@dns_type == 3))
|
388
|
+
dns_params = {:nameserver=>@dns_server}
|
389
|
+
end
|
390
|
+
|
391
|
+
Dnsruby::DNS.open(dns_params) {|dns|
|
277
392
|
begin
|
278
393
|
arrayTXT = dns.getresources(strDomain, Dnsruby::Types.TXT)
|
279
394
|
if arrayTXT.to_s != "[]"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wlvalidate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
13
|
-
dependencies:
|
12
|
+
date: 2013-02-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: dnsruby
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
description: SendGrid Rails Gem to validate White Label settings
|
15
31
|
email: jayson.sperling@sendgrid.com
|
16
32
|
executables: []
|
@@ -38,7 +54,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
54
|
version: '0'
|
39
55
|
requirements: []
|
40
56
|
rubyforge_project:
|
41
|
-
rubygems_version: 1.8.
|
57
|
+
rubygems_version: 1.8.24
|
42
58
|
signing_key:
|
43
59
|
specification_version: 3
|
44
60
|
summary: SendGrid White Label Validator
|