1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.james.jspf.core;
22
23
24
25
26
27
28
29 import org.apache.james.jspf.core.exceptions.NeutralException;
30 import org.apache.james.jspf.core.exceptions.NoneException;
31 import org.apache.james.jspf.core.exceptions.PermErrorException;
32 import org.apache.james.jspf.core.exceptions.TempErrorException;
33 import org.apache.james.jspf.core.exceptions.TimeoutException;
34
35 import java.io.UnsupportedEncodingException;
36 import java.net.URLEncoder;
37 import java.util.ArrayList;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
42
43 public class MacroExpand {
44
45 private Pattern domainSpecPattern;
46
47 private Pattern macroStringPattern;
48
49 private Pattern macroLettersPattern;
50
51 private Pattern macroLettersExpPattern;
52
53 private Pattern cellPattern;
54
55 private Logger log;
56
57 private DNSService dnsProbe;
58
59 public static final boolean EXPLANATION = true;
60
61 public static final boolean DOMAIN = false;
62
63 public static class RequireClientDomainException extends Exception {
64
65 private static final long serialVersionUID = 3834282981657676530L;
66
67 }
68
69
70
71
72
73
74
75 public MacroExpand(Logger logger, DNSService dnsProbe) {
76
77 domainSpecPattern = Pattern.compile(SPFTermsRegexps.DOMAIN_SPEC_REGEX_R);
78
79 macroStringPattern = Pattern.compile(SPFTermsRegexps.MACRO_STRING_REGEX_TOKEN);
80
81 macroLettersExpPattern = Pattern.compile(SPFTermsRegexps.MACRO_LETTER_PATTERN_EXP);
82 macroLettersPattern = Pattern.compile(SPFTermsRegexps.MACRO_LETTER_PATTERN);
83 log = logger;
84 this.dnsProbe = dnsProbe;
85 }
86
87
88 private static final class AResponseListener implements
89 SPFCheckerDNSResponseListener {
90
91
92
93
94 public DNSLookupContinuation onDNSResponse(DNSResponse response, SPFSession session)
95 throws PermErrorException, NoneException, TempErrorException,
96 NeutralException {
97
98
99 session.setClientDomain("unknown");
100 try {
101 List records = response.getResponse();
102 if (records != null && records.size() > 0) {
103 Iterator i = records.iterator();
104 while (i.hasNext()) {
105 String next = (String) i.next();
106 if (IPAddr.getAddress(session.getIpAddress())
107 .toString().equals(
108 IPAddr.getAddress(next).toString())) {
109 session
110 .setClientDomain((String) session
111 .getAttribute(ATTRIBUTE_MACRO_EXPAND_CHECKED_RECORD));
112 break;
113 }
114 }
115 }
116 } catch (TimeoutException e) {
117
118 } catch (PermErrorException e) {
119
120 }
121 return null;
122 }
123 }
124
125 private static final class PTRResponseListener implements
126 SPFCheckerDNSResponseListener {
127
128
129
130
131 public DNSLookupContinuation onDNSResponse(DNSResponse response, SPFSession session)
132 throws PermErrorException, NoneException, TempErrorException,
133 NeutralException {
134
135 try {
136 boolean ip6 = IPAddr.isIPV6(session.getIpAddress());
137 List records = response.getResponse();
138
139 if (records != null && records.size() > 0) {
140 String record = (String) records.get(0);
141 session.setAttribute(ATTRIBUTE_MACRO_EXPAND_CHECKED_RECORD,
142 record);
143
144 return new DNSLookupContinuation(new DNSRequest(record,
145 ip6 ? DNSRequest.AAAA : DNSRequest.A),
146 new AResponseListener());
147
148 }
149 } catch (TimeoutException e) {
150
151 session.setClientDomain("unknown");
152 }
153 return null;
154
155 }
156 }
157
158 private static final String ATTRIBUTE_MACRO_EXPAND_CHECKED_RECORD = "MacroExpand.checkedRecord";
159
160 public DNSLookupContinuation checkExpand(String input, SPFSession session, boolean isExplanation) throws PermErrorException, NoneException {
161 if (input != null) {
162 String host = this.expand(input, session, isExplanation);
163 if (host == null) {
164
165 return new DNSLookupContinuation(new DNSRequest(IPAddr
166 .getAddress(session.getIpAddress()).getReverseIP(),
167 DNSRequest.PTR), new PTRResponseListener());
168 }
169 }
170 return null;
171 }
172
173 public String expand(String input, MacroData macroData, boolean isExplanation) throws PermErrorException {
174 try {
175 if (isExplanation) {
176 return expandExplanation(input, macroData);
177 } else {
178 return expandDomain(input, macroData);
179 }
180 } catch (RequireClientDomainException e) {
181 return null;
182 }
183 }
184
185
186
187
188
189
190
191
192
193
194
195 private String expandExplanation(String input, MacroData macroData) throws PermErrorException, RequireClientDomainException {
196
197 log.debug("Start do expand explanation: " + input);
198
199 String[] parts = input.split(" ");
200 StringBuffer res = new StringBuffer();
201 for (int i = 0; i < parts.length; i++) {
202 if (i > 0) res.append(" ");
203 res.append(expandMacroString(parts[i], macroData, true));
204 }
205 log.debug("Done expand explanation: " + res);
206
207 return res.toString();
208 }
209
210
211
212
213
214
215
216
217
218
219
220 private String expandDomain(String input, MacroData macroData) throws PermErrorException, RequireClientDomainException {
221
222 log.debug("Start expand domain: " + input);
223
224 Matcher inputMatcher = domainSpecPattern.matcher(input);
225 if (!inputMatcher.matches() || inputMatcher.groupCount() != 2) {
226 throw new PermErrorException("Invalid DomainSpec: "+input);
227 }
228
229 StringBuffer res = new StringBuffer();
230 if (inputMatcher.group(1) != null && inputMatcher.group(1).length() > 0) {
231 res.append(expandMacroString(inputMatcher.group(1), macroData, false));
232 }
233 if (inputMatcher.group(2) != null && inputMatcher.group(2).length() > 0) {
234 if (inputMatcher.group(2).startsWith(".")) {
235 res.append(inputMatcher.group(2));
236 } else {
237 res.append(expandMacroString(inputMatcher.group(2), macroData, false));
238 }
239 }
240
241 String domainName = expandMacroString(input, macroData, false);
242
243 int split = 0;
244 while (domainName.length() > 255 && split > -1) {
245 split = domainName.indexOf(".");
246 domainName = domainName.substring(split + 1);
247 }
248
249 log.debug("Domain expanded: " + domainName);
250
251 return domainName;
252 }
253
254
255
256
257
258
259
260
261
262
263
264 private String expandMacroString(String input, MacroData macroData, boolean isExplanation) throws PermErrorException, RequireClientDomainException {
265
266 StringBuffer decodedValue = new StringBuffer();
267 Matcher inputMatcher = macroStringPattern.matcher(input);
268 String macroCell;
269 int pos = 0;
270
271 while (inputMatcher.find()) {
272 String match2 = inputMatcher.group();
273 if (pos != inputMatcher.start()) {
274 throw new PermErrorException("Middle part does not match: "+input.substring(0,pos)+">>"+input.substring(pos, inputMatcher.start())+"<<"+input.substring(inputMatcher.start())+" ["+input+"]");
275 }
276 if (match2.length() > 0) {
277 if (match2.startsWith("%{")) {
278 macroCell = input.substring(inputMatcher.start() + 2, inputMatcher
279 .end() - 1);
280 inputMatcher
281 .appendReplacement(decodedValue, escapeForMatcher(replaceCell(macroCell, macroData, isExplanation)));
282 } else if (match2.length() == 2 && match2.startsWith("%")) {
283
284
285
286
287
288
289
290
291 if ("%_".equals(match2)) {
292 inputMatcher.appendReplacement(decodedValue, " ");
293 } else if ("%-".equals(match2)) {
294 inputMatcher.appendReplacement(decodedValue, "%20");
295 } else {
296 inputMatcher.appendReplacement(decodedValue, escapeForMatcher(match2.substring(1)));
297 }
298 }
299 }
300
301 pos = inputMatcher.end();
302 }
303
304 if (input.length() != pos) {
305 throw new PermErrorException("End part does not match: "+input.substring(pos));
306 }
307
308 inputMatcher.appendTail(decodedValue);
309
310 return decodedValue.toString();
311 }
312
313
314
315
316
317
318
319
320
321
322
323 private String replaceCell(String replaceValue, MacroData macroData, boolean isExplanation) throws PermErrorException, RequireClientDomainException {
324
325 String variable = "";
326 String domainNumber = "";
327 boolean isReversed = false;
328 String delimeters = ".";
329
330
331
332
333 String commandCharacter = replaceValue.substring(0, 1);
334 Matcher cellMatcher;
335
336 if (isExplanation) {
337 cellMatcher = macroLettersExpPattern.matcher(commandCharacter);
338 } else {
339 cellMatcher = macroLettersPattern.matcher(commandCharacter);
340 }
341 if (cellMatcher.find()) {
342 if (cellMatcher.group().toUpperCase().equals(cellMatcher.group())) {
343 variable = encodeURL(matchMacro(cellMatcher.group(), macroData));
344 } else {
345 variable = matchMacro(cellMatcher.group(), macroData);
346 }
347
348
349 replaceValue = replaceValue.substring(1);
350 } else {
351 throw new PermErrorException("MacroLetter not found: "+replaceValue);
352 }
353
354
355 cellPattern = Pattern.compile("\\d+");
356 cellMatcher = cellPattern.matcher(replaceValue);
357 while (cellMatcher.find()) {
358 domainNumber = cellMatcher.group();
359 if (Integer.parseInt(domainNumber) == 0) {
360 throw new PermErrorException(
361 "Digit transformer must be non-zero");
362 }
363 }
364
365 cellPattern = Pattern.compile("r");
366 cellMatcher = cellPattern.matcher(replaceValue);
367 while (cellMatcher.find()) {
368 isReversed = true;
369 }
370
371
372 cellPattern = Pattern.compile("[\\.\\-\\+\\,\\/\\_\\=]+");
373 cellMatcher = cellPattern.matcher(replaceValue);
374 while (cellMatcher.find()) {
375 delimeters = cellMatcher.group();
376 }
377
378
379 ArrayList data = split(variable, delimeters);
380 if (isReversed) {
381 data = reverse(data);
382 }
383
384
385 String returnData;
386 if (!domainNumber.equals("")) {
387 returnData = subset(data, Integer.parseInt(domainNumber));
388 } else {
389 returnData = subset(data);
390 }
391
392 return returnData;
393
394 }
395
396
397
398
399
400
401
402
403
404
405
406
407 private String matchMacro(String macro, MacroData macroData) throws PermErrorException, RequireClientDomainException {
408
409 String rValue = null;
410
411 String variable = macro.toLowerCase();
412 if (variable.equalsIgnoreCase("i")) {
413 rValue = macroData.getMacroIpAddress();
414 } else if (variable.equalsIgnoreCase("s")) {
415 rValue = macroData.getMailFrom();
416 } else if (variable.equalsIgnoreCase("h")) {
417 rValue = macroData.getHostName();
418 } else if (variable.equalsIgnoreCase("l")) {
419 rValue = macroData.getCurrentSenderPart();
420 } else if (variable.equalsIgnoreCase("d")) {
421 rValue = macroData.getCurrentDomain();
422 } else if (variable.equalsIgnoreCase("v")) {
423 rValue = macroData.getInAddress();
424 } else if (variable.equalsIgnoreCase("t")) {
425 rValue = Long.toString(macroData.getTimeStamp());
426 } else if (variable.equalsIgnoreCase("c")) {
427 rValue = macroData.getReadableIP();
428 } else if (variable.equalsIgnoreCase("p")) {
429 rValue = macroData.getClientDomain();
430 if (rValue == null) {
431 throw new RequireClientDomainException();
432 }
433 } else if (variable.equalsIgnoreCase("o")) {
434 rValue = macroData.getSenderDomain();
435 } else if (variable.equalsIgnoreCase("r")) {
436 rValue = macroData.getReceivingDomain();
437 if (rValue == null) {
438 rValue = "unknown";
439 List dNames = dnsProbe.getLocalDomainNames();
440
441 for (int i = 0; i < dNames.size(); i++) {
442
443 if (SPF1Utils.checkFQDN(dNames.get(i).toString())) {
444 rValue = dNames.get(i).toString();
445 if (macroData instanceof SPFSession) {
446 ((SPFSession) macroData).setReceivingDomain(rValue);
447 }
448 break;
449 }
450 }
451 }
452 }
453
454 if (rValue == null) {
455 throw new PermErrorException("Unknown command : " + variable);
456
457 } else {
458 log.debug("Used macro: " + macro + " replaced with: " + rValue);
459
460 return rValue;
461 }
462 }
463
464
465
466
467
468
469
470
471
472
473
474
475 private ArrayList split(String data, String delimeters) {
476
477 String currentChar;
478 StringBuffer element = new StringBuffer();
479 ArrayList splitParts = new ArrayList();
480
481 for (int i = 0; i < data.length(); i++) {
482 currentChar = data.substring(i, i + 1);
483 if (delimeters.indexOf(currentChar) > -1) {
484 splitParts.add(element.toString());
485 element.setLength(0);
486 } else {
487 element.append(currentChar);
488 }
489 }
490 splitParts.add(element.toString());
491 return splitParts;
492 }
493
494
495
496
497
498
499
500
501 private ArrayList reverse(ArrayList data) {
502
503 ArrayList reversed = new ArrayList();
504 for (int i = 0; i < data.size(); i++) {
505 reversed.add(0, data.get(i));
506 }
507 return reversed;
508 }
509
510
511
512
513 private String subset(ArrayList data) {
514 return subset(data, data.size());
515 }
516
517
518
519
520
521
522
523
524 private String subset(ArrayList data, int length) {
525
526 StringBuffer buildString = new StringBuffer();
527 if (data.size() < length) {
528 length = data.size();
529 }
530 int start = data.size() - length;
531 for (int i = start; i < data.size(); i++) {
532 if (buildString.length() > 0) {
533 buildString.append(".");
534 }
535 buildString.append(data.get(i));
536 }
537 return buildString.toString();
538
539 }
540
541
542
543
544
545
546
547
548 private String encodeURL(String data) {
549
550 try {
551
552
553
554
555
556 data = URLEncoder.encode(data, "UTF-8");
557 } catch (UnsupportedEncodingException e) {
558
559 }
560
561
562 return data.replaceAll("\\+", "%20");
563
564 }
565
566
567
568
569
570
571
572
573
574
575 private String escapeForMatcher(String raw) {
576 StringBuffer sb = new StringBuffer();
577
578 for (int i = 0; i < raw.length(); i++) {
579 char c = raw.charAt(i);
580 if (c == '$' || c == '\\') {
581 sb.append('\\');
582 }
583 sb.append(c);
584 }
585 return sb.toString();
586 }
587
588 }