1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.james.jspf;
21
22 import org.apache.james.jspf.core.DNSRequest;
23 import org.apache.james.jspf.core.DNSService;
24 import org.apache.james.jspf.core.DNSServiceEnabled;
25 import org.apache.james.jspf.core.LogEnabled;
26 import org.apache.james.jspf.core.Logger;
27 import org.apache.james.jspf.core.MacroExpand;
28 import org.apache.james.jspf.core.MacroExpandEnabled;
29 import org.apache.james.jspf.core.SPFCheckEnabled;
30 import org.apache.james.jspf.core.SPFRecordParser;
31 import org.apache.james.jspf.core.exceptions.TimeoutException;
32 import org.apache.james.jspf.tester.DNSTestingServer;
33 import org.apache.james.jspf.executor.SPFExecutor;
34 import org.apache.james.jspf.executor.SPFResult;
35 import org.apache.james.jspf.executor.StagedMultipleSPFExecutor;
36 import org.apache.james.jspf.executor.SynchronousSPFExecutor;
37 import org.apache.james.jspf.impl.DNSJnioAsynchService;
38 import org.apache.james.jspf.impl.DNSServiceAsynchSimulator;
39 import org.apache.james.jspf.impl.DNSServiceXBillImpl;
40 import org.apache.james.jspf.impl.DefaultTermsFactory;
41 import org.apache.james.jspf.impl.SPF;
42 import org.apache.james.jspf.parser.RFC4408SPF1Parser;
43 import org.apache.james.jspf.wiring.WiringService;
44 import org.apache.james.jspf.wiring.WiringServiceException;
45 import org.apache.james.jspf.tester.SPFYamlTestDescriptor;
46 import org.xbill.DNS.Cache;
47 import org.xbill.DNS.DClass;
48 import org.xbill.DNS.ExtendedNonblockingResolver;
49 import org.xbill.DNS.Lookup;
50 import org.xbill.DNS.LookupAsynch;
51 import org.xbill.DNS.Name;
52 import org.xbill.DNS.NonblockingResolver;
53 import org.xbill.DNS.Resolver;
54 import org.xbill.DNS.SimpleResolver;
55 import org.xbill.DNS.TextParseException;
56
57 import java.io.IOException;
58 import java.net.UnknownHostException;
59 import java.util.ArrayList;
60 import java.util.HashMap;
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.Locale;
64 import java.util.Map;
65
66 import junit.framework.AssertionFailedError;
67 import junit.framework.TestCase;
68
69 public abstract class AbstractYamlTest extends TestCase {
70
71 private static final int FAKE_SERVER_PORT = 31348;
72 protected static final int TIMEOUT = 10;
73 protected static final int MOCK_SERVICE = 2;
74 protected static final int FAKE_SERVER = 1;
75 protected static final int REAL_SERVER = 3;
76 private int dnsServiceMockStyle = MOCK_SERVICE;
77
78 protected static final int SYNCHRONOUS_EXECUTOR = 1;
79 protected static final int STAGED_EXECUTOR = 2;
80 protected static final int STAGED_EXECUTOR_MULTITHREADED = 3;
81 protected static final int STAGED_EXECUTOR_DNSJNIO = 4;
82 private int spfExecutorType = SYNCHRONOUS_EXECUTOR;
83
84 SPFYamlTestDescriptor data;
85 String test;
86 protected Logger log;
87 private SPFExecutor executor;
88 protected static MacroExpand macroExpand;
89 protected static SPF spf;
90 protected static SPFYamlTestDescriptor prevData;
91 protected static SPFRecordParser parser;
92 private static DNSService dns;
93 protected static DNSTestingServer dnsTestServer;
94
95 protected AbstractYamlTest(SPFYamlTestDescriptor def, String test) {
96 super(def.getComment()+" #"+test);
97 this.data = def;
98 this.test = test;
99 }
100
101 protected AbstractYamlTest(SPFYamlTestDescriptor def) {
102 super(def.getComment()+" #COMPLETE!");
103 this.data = def;
104 this.test = null;
105 }
106
107 protected abstract String getFilename();
108
109 protected AbstractYamlTest(String name) throws IOException {
110 super(name);
111 List tests = SPFYamlTestDescriptor.loadTests(getFilename());
112 Iterator i = tests.iterator();
113 while (i.hasNext() && data == null) {
114 SPFYamlTestDescriptor def = (SPFYamlTestDescriptor) i.next();
115 if (name.equals(def.getComment()+" #COMPLETE!")) {
116 data = def;
117 this.test = null;
118 } else {
119 Iterator j = def.getTests().keySet().iterator();
120 while (j.hasNext() && data == null) {
121 String test = (String) j.next();
122 if (name.equals(def.getComment()+ " #"+test)) {
123 data = def;
124 this.test = test;
125 }
126 }
127 }
128 }
129 assertNotNull(data);
130
131 }
132
133 protected void runTest() throws Throwable {
134
135 if (log == null) {
136 log = new ConsoleLogger(ConsoleLogger.LEVEL_DEBUG, "root");
137 }
138
139 log.info("Running test: "+getName()+" ...");
140
141 if (parser == null) {
142
143
144
145
146 parser = new RFC4408SPF1Parser(log.getChildLogger("parser"), new DefaultTermsFactory(log.getChildLogger("termsfactory"), new WiringService() {
147
148 public void wire(Object component) throws WiringServiceException {
149 if (component instanceof LogEnabled) {
150 String[] path = component.getClass().toString().split("\\.");
151 ((LogEnabled) component).enableLogging(log.getChildLogger("dep").getChildLogger(path[path.length-1].toLowerCase()));
152 }
153 if (component instanceof MacroExpandEnabled) {
154 ((MacroExpandEnabled) component).enableMacroExpand(macroExpand);
155 }
156 if (component instanceof DNSServiceEnabled) {
157 ((DNSServiceEnabled) component).enableDNSService(dns);
158 }
159 if (component instanceof SPFCheckEnabled) {
160 ((SPFCheckEnabled) component).enableSPFChecking(spf);
161 }
162 }
163
164 }));
165 }
166 if (this.data != AbstractYamlTest.prevData) {
167 dns = new LoggingDNSService(getDNSService(), log.getChildLogger("dns"));
168 AbstractYamlTest.prevData = this.data;
169 }
170 macroExpand = new MacroExpand(log.getChildLogger("macroExpand"), dns);
171 if (getSpfExecutorType() == SYNCHRONOUS_EXECUTOR) {
172 executor = new SynchronousSPFExecutor(log, dns);
173 } else if (getSpfExecutorType() == STAGED_EXECUTOR || getSpfExecutorType() == STAGED_EXECUTOR_MULTITHREADED){
174 executor = new StagedMultipleSPFExecutor(log, new DNSServiceAsynchSimulator(dns, getSpfExecutorType() == STAGED_EXECUTOR_MULTITHREADED));
175 } else if (getSpfExecutorType() == STAGED_EXECUTOR_DNSJNIO) {
176
177
178 LookupAsynch.setDefaultCache(new Cache(), DClass.IN);
179
180 LookupAsynch.getDefaultCache(DClass.IN).clearCache();
181
182 try {
183 ExtendedNonblockingResolver resolver;
184
185 if (getDnsServiceMockStyle() == FAKE_SERVER) {
186 NonblockingResolver nonblockingResolver = new NonblockingResolver("127.0.0.1");
187 resolver = ExtendedNonblockingResolver.newInstance(new NonblockingResolver[] {nonblockingResolver});
188 nonblockingResolver.setPort(FAKE_SERVER_PORT);
189 nonblockingResolver.setTCP(false);
190 } else if (getDnsServiceMockStyle() == REAL_SERVER) {
191 resolver = ExtendedNonblockingResolver.newInstance();
192 Resolver[] resolvers = resolver.getResolvers();
193 for (int i = 0; i < resolvers.length; i++) {
194 resolvers[i].setTCP(false);
195 }
196 } else {
197 throw new IllegalStateException("DnsServiceMockStyle "+getDnsServiceMockStyle()+" is not supported when STAGED_EXECUTOR_DNSJNIO executor style is used");
198 }
199
200 DNSJnioAsynchService jnioAsynchService = new DNSJnioAsynchService(resolver);
201 jnioAsynchService.setTimeout(TIMEOUT);
202 executor = new StagedMultipleSPFExecutor(log, jnioAsynchService);
203
204 } catch (UnknownHostException e) {
205
206 e.printStackTrace();
207 }
208
209 } else {
210 throw new UnsupportedOperationException("Unknown executor type");
211 }
212 spf = new SPF(dns, parser, log.getChildLogger("spf"), macroExpand, executor);
213
214 if (test != null) {
215 String next = test;
216 SPFResult res = runSingleTest(next);
217 verifyResult(next, res);
218 } else {
219 Map queries = new HashMap();
220 for (Iterator i = data.getTests().keySet().iterator(); i.hasNext(); ) {
221 String next = (String) i.next();
222 SPFResult res = runSingleTest(next);
223 queries.put(next, res);
224 }
225 AssertionFailedError firstError = null;
226 for (Iterator i = queries.keySet().iterator(); i.hasNext(); ) {
227 String next = (String) i.next();
228 try {
229 verifyResult(next, (SPFResult) queries.get(next));
230 } catch (AssertionFailedError e) {
231 log.getChildLogger(next).info("FAILED. "+e.getMessage()+" ("+getName()+")", e.getMessage()==null ? e : null);
232 if (firstError == null) firstError = e;
233 }
234 }
235 if (firstError != null) throw firstError;
236 }
237
238 }
239
240 private SPFResult runSingleTest(String testName) {
241 HashMap currentTest = (HashMap) data.getTests().get(testName);
242 Logger testLogger = log.getChildLogger(testName);
243 testLogger.info("TESTING "+testName+": "+currentTest.get("description"));
244
245 String ip = null;
246 String sender = null;
247 String helo = null;
248
249 if (currentTest.get("helo") != null) {
250 helo = (String) currentTest.get("helo");
251 }
252 if (currentTest.get("host") != null) {
253 ip = (String) currentTest.get("host");
254 }
255 if (currentTest.get("mailfrom") != null) {
256 sender = (String) currentTest.get("mailfrom");
257 } else {
258 sender = "";
259 }
260
261 SPFResult res = spf.checkSPF(ip, sender, helo);
262 return res;
263 }
264
265 private void verifyResult(String testName, SPFResult res) {
266 String resultSPF = res.getResult();
267 HashMap currentTest = (HashMap) data.getTests().get(testName);
268 Logger testLogger = log.getChildLogger(testName+"-verify");
269 if (currentTest.get("result") instanceof String) {
270 assertEquals("Test "+testName+" ("+currentTest.get("description")+") failed. Returned: "+resultSPF+" Expected: "+currentTest.get("result")+" [["+resultSPF+"||"+res.getHeaderText()+"]]", currentTest.get("result"), resultSPF);
271 } else {
272 ArrayList results = (ArrayList) currentTest.get("result");
273 boolean match = false;
274 for (int i = 0; i < results.size(); i++) {
275 if (results.get(i).equals(resultSPF)) match = true;
276
277 }
278 assertTrue("Test "+testName+" ("+currentTest.get("description")+") failed. Returned: "+resultSPF+" Expected: "+results, match);
279 }
280
281 if (currentTest.get("explanation") != null) {
282
283
284 if (currentTest.get("explanation").equals("DEFAULT")) {
285 assertTrue(res.getExplanation().startsWith("http://www.openspf.org/why.html?sender="));
286 } else if (currentTest.get("explanation").equals("cafe:babe::1 is queried as 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa")) {
287
288
289
290 assertTrue(res.getExplanation().equals("cafe:babe:0:0:0:0:0:1 is queried as 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa"));
291 } else {
292 assertEquals(currentTest.get("explanation"),res.getExplanation());
293 }
294
295 }
296
297 testLogger.info("PASSED. Result="+resultSPF+" Explanation="+res.getExplanation()+" Header="+res.getHeaderText());
298 }
299
300
301
302
303 protected DNSService getDNSServiceMockedDNSService() {
304 SPFYamlDNSService yamlDNSService = new SPFYamlDNSService(data.getZonedata());
305 return yamlDNSService;
306 }
307
308
309
310
311 protected DNSService getDNSService() {
312 switch (getDnsServiceMockStyle()) {
313 case MOCK_SERVICE: return getDNSServiceMockedDNSService();
314 case FAKE_SERVER: return getDNSServiceFakeServer();
315 case REAL_SERVER: return getDNSServiceReal();
316 default:
317 throw new UnsupportedOperationException("Unsupported mock style");
318 }
319 }
320
321 protected int getDnsServiceMockStyle() {
322 return dnsServiceMockStyle;
323 }
324
325
326
327
328 protected DNSService getDNSServiceFakeServer() {
329 Resolver resolver = null;
330 try {
331 resolver = new SimpleResolver("127.0.0.1");
332 } catch (UnknownHostException e) {
333
334 e.printStackTrace();
335 }
336 resolver.setPort(FAKE_SERVER_PORT);
337 Lookup.setDefaultResolver(resolver);
338 Lookup.setDefaultCache(null, DClass.IN);
339 Lookup.setDefaultSearchPath(new Name[] {});
340
341 if (dnsTestServer == null) {
342 try {
343 dnsTestServer = new DNSTestingServer("0.0.0.0", ""+FAKE_SERVER_PORT);
344 } catch (TextParseException e) {
345 throw new RuntimeException("Error trying to instantiate the testing dns server.", e);
346 } catch (IOException e) {
347 throw new RuntimeException("Error trying to instantiate the testing dns server.", e);
348 }
349 }
350
351 dnsTestServer.setData(data.getZonedata());
352
353 DNSServiceXBillImpl serviceXBillImpl = new DNSServiceXBillImpl(log) {
354
355 public List getLocalDomainNames() {
356 List l = new ArrayList();
357 l.add("localdomain.foo.bar");
358 return l;
359 }
360
361 };
362
363 serviceXBillImpl.setTimeOut(TIMEOUT);
364 return serviceXBillImpl;
365 }
366
367
368
369
370 protected DNSService getDNSServiceReal() {
371 DNSServiceXBillImpl serviceXBillImpl = new DNSServiceXBillImpl(log);
372
373 serviceXBillImpl.setTimeOut(TIMEOUT);
374 return serviceXBillImpl;
375 }
376
377 public AbstractYamlTest() {
378 super();
379 }
380
381 final class SPFYamlDNSService implements DNSService {
382
383 private HashMap zonedata;
384 private int recordLimit;
385
386 public SPFYamlDNSService(HashMap zonedata) {
387 this.zonedata = zonedata;
388 this.recordLimit = 10;
389 }
390
391 public List getLocalDomainNames() {
392 List l = new ArrayList();
393 l.add("localdomain.foo.bar");
394 return l;
395 }
396
397 public void setTimeOut(int timeOut) {
398 try {
399 throw new UnsupportedOperationException("setTimeOut()");
400 } catch (UnsupportedOperationException e) {
401 e.printStackTrace();
402 throw e;
403 }
404 }
405
406 public int getRecordLimit() {
407 return recordLimit;
408 }
409
410 public void setRecordLimit(int recordLimit) {
411 this.recordLimit = recordLimit;
412 }
413
414 public List getRecords(DNSRequest request) throws TimeoutException {
415 return getRecords(request.getHostname(), request.getRecordType(), 6);
416 }
417
418 public List getRecords(String hostname, int recordType, int depth) throws TimeoutException {
419 String type = getRecordTypeDescription(recordType);
420
421 List res;
422
423
424 if (hostname.endsWith(".")) hostname = hostname.substring(0, hostname.length()-1);
425
426
427 hostname = hostname.toLowerCase(Locale.US);
428
429 if (zonedata.get(hostname) != null) {
430 List l = (List) zonedata.get(hostname);
431 Iterator i = l.iterator();
432 res = new ArrayList();
433 while (i.hasNext()) {
434 Object o = i.next();
435 if (o instanceof HashMap) {
436 HashMap hm = (HashMap) o;
437 if (hm.get(type) != null) {
438 if (recordType == DNSRequest.MX) {
439 List mxList = (List) hm.get(type);
440
441
442 Iterator mxs = mxList.iterator();
443 while (mxs.hasNext()) {
444
445 mxs.next();
446 String cname = (String) mxs.next();
447 res.add(cname);
448 }
449 } else {
450 Object obj = hm.get(type);
451
452 if (obj instanceof String) {
453 res.add(obj);
454 } else if (obj instanceof ArrayList) {
455 ArrayList a = (ArrayList) obj;
456 StringBuffer sb = new StringBuffer();
457
458 for (int i2 = 0; i2 < a.size(); i2++) {
459 sb.append(a.get(i2));
460 }
461 res.add(sb.toString());
462 }
463 }
464 }
465 if (hm.get("CNAME") != null && depth > 0) {
466 return getRecords((String) hm.get("CNAME"), recordType, depth - 1);
467 }
468 } else if ("TIMEOUT".equals(o)) {
469 throw new TimeoutException("TIMEOUT");
470 } else {
471 throw new IllegalStateException("getRecord found an unexpected data");
472 }
473 }
474 return res.size() > 0 ? res : null;
475 }
476 return null;
477 }
478
479 }
480
481
482
483
484
485
486
487
488 public static String getRecordTypeDescription(int recordType) {
489 switch (recordType) {
490 case DNSRequest.A: return "A";
491 case DNSRequest.AAAA: return "AAAA";
492 case DNSRequest.MX: return "MX";
493 case DNSRequest.PTR: return "PTR";
494 case DNSRequest.TXT: return "TXT";
495 case DNSRequest.SPF: return "SPF";
496 default: return null;
497 }
498 }
499
500 protected int getSpfExecutorType() {
501 return spfExecutorType;
502 }
503
504 }