1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.james.smtpserver.core.filter.fastfail;
23
24 import java.util.ArrayList;
25 import java.util.Collection;
26
27 import org.apache.avalon.framework.activity.Initializable;
28 import org.apache.avalon.framework.configuration.Configuration;
29 import org.apache.avalon.framework.configuration.ConfigurationException;
30 import org.apache.james.dsn.DSNStatus;
31 import org.apache.james.jspf.impl.DefaultSPF;
32 import org.apache.james.jspf.impl.SPF;
33 import org.apache.james.jspf.core.DNSService;
34 import org.apache.james.jspf.core.exceptions.SPFErrorConstants;
35 import org.apache.james.jspf.executor.SPFResult;
36 import org.apache.james.smtpserver.CommandHandler;
37 import org.apache.james.smtpserver.MessageHandler;
38 import org.apache.james.smtpserver.SMTPSession;
39 import org.apache.mailet.Mail;
40 import org.apache.mailet.MailAddress;
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public class SPFHandler extends AbstractJunkHandler implements CommandHandler,
55 MessageHandler,Initializable {
56
57 public static final String SPF_BLOCKLISTED = "SPF_BLOCKLISTED";
58
59 public static final String SPF_DETAIL = "SPF_DETAIL";
60
61 public static final String SPF_TEMPBLOCKLISTED = "SPF_TEMPBLOCKLISTED";
62
63 public final static String SPF_HEADER = "SPF_HEADER";
64
65 public final static String SPF_HEADER_MAIL_ATTRIBUTE_NAME = "org.apache.james.spf.header";
66
67
68
69
70 private boolean blockSoftFail = false;
71
72 private boolean blockPermError = true;
73
74 private DNSService dnsService = null;
75
76 private boolean checkAuthNetworks = false;
77
78 private SPF spf;
79
80
81
82
83
84
85 public void configure(Configuration handlerConfiguration)
86 throws ConfigurationException {
87 Configuration configuration = handlerConfiguration.getChild(
88 "blockSoftFail", false);
89 if (configuration != null) {
90 setBlockSoftFail(configuration.getValueAsBoolean(false));
91 }
92
93 Configuration configPermError = handlerConfiguration.getChild(
94 "blockPermError", false);
95 if (configuration != null) {
96 setBlockPermError(configPermError.getValueAsBoolean(true));
97 }
98 Configuration configRelay = handlerConfiguration.getChild(
99 "checkAuthNetworks", false);
100 if (configRelay != null) {
101 setCheckAuthNetworks(configRelay.getValueAsBoolean(false));
102 }
103
104 super.configure(handlerConfiguration);
105
106 }
107
108
109
110
111
112 public void initialize() throws Exception {
113 if (dnsService == null) {
114 spf = new DefaultSPF(new SPFLoggerAdapter(getLogger()));
115 } else {
116 spf = new SPF(dnsService, new SPFLoggerAdapter(getLogger()));
117 }
118 }
119
120
121
122
123
124
125
126 public void setBlockSoftFail(boolean blockSoftFail) {
127 this.blockSoftFail = blockSoftFail;
128 }
129
130
131
132
133
134
135
136 public void setBlockPermError(boolean blockPermError) {
137 this.blockPermError = blockPermError;
138 }
139
140
141
142
143
144
145 public void setDNSService(DNSService dnsService) {
146 this.dnsService = dnsService;
147 }
148
149
150
151
152
153
154
155 public void setCheckAuthNetworks(boolean checkAuthNetworks) {
156 this.checkAuthNetworks = checkAuthNetworks;
157 }
158
159
160
161
162
163
164 public void onCommand(SMTPSession session) {
165 if (session.getCommandName().equals("MAIL")) {
166 doSPFCheck(session);
167 } else if (session.getCommandName().equals("RCPT")) {
168 doProcessing(session);
169 }
170 }
171
172
173
174
175
176
177
178 private void doSPFCheck(SMTPSession session) {
179
180 MailAddress sender = (MailAddress) session.getState().get(
181 SMTPSession.SENDER);
182 String heloEhlo = (String) session.getState().get(
183 SMTPSession.CURRENT_HELO_NAME);
184
185
186 if (sender == null || heloEhlo == null) {
187 getLogger().info("No Sender or HELO/EHLO present");
188 } else {
189
190 if (session.isRelayingAllowed() && checkAuthNetworks == false) {
191 getLogger().info(
192 "Ipaddress " + session.getRemoteIPAddress()
193 + " is allowed to relay. Don't check it");
194 } else {
195
196 String ip = session.getRemoteIPAddress();
197
198 SPFResult result = spf
199 .checkSPF(ip, sender.toString(), heloEhlo);
200
201 String spfResult = result.getResult();
202
203 String explanation = "Blocked - see: "
204 + result.getExplanation();
205
206
207 session.getState().put(SPF_HEADER, result.getHeaderText());
208
209 getLogger().info(
210 "Result for " + ip + " - " + sender + " - " + heloEhlo
211 + " = " + spfResult);
212
213
214 if ((spfResult.equals(SPFErrorConstants.FAIL_CONV))
215 || (spfResult.equals(SPFErrorConstants.SOFTFAIL_CONV) && blockSoftFail)
216 || (spfResult.equals(SPFErrorConstants.PERM_ERROR_CONV) && blockPermError)) {
217
218 if (spfResult.equals(SPFErrorConstants.PERM_ERROR_CONV)) {
219 explanation = "Block caused by an invalid SPF record";
220 }
221 session.getState().put(SPF_DETAIL, explanation);
222 session.getState().put(SPF_BLOCKLISTED, "true");
223
224 } else if (spfResult.equals(SPFErrorConstants.TEMP_ERROR_CONV)) {
225 session.getState().put(SPF_TEMPBLOCKLISTED, "true");
226 }
227 }
228 }
229
230 }
231
232
233
234
235 public Collection getImplCommands() {
236 Collection commands = new ArrayList();
237 commands.add("MAIL");
238 commands.add("RCPT");
239
240 return commands;
241 }
242
243
244
245
246 public void onMessage(SMTPSession session) {
247 Mail mail = session.getMail();
248
249
250 mail.setAttribute(SPF_HEADER_MAIL_ATTRIBUTE_NAME, (String) session.getState().get(SPF_HEADER));
251 }
252
253
254
255
256
257 protected boolean check(SMTPSession session) {
258 MailAddress recipientAddress = (MailAddress) session.getState().get(
259 SMTPSession.CURRENT_RECIPIENT);
260 String blocklisted = (String) session.getState().get(SPF_BLOCKLISTED);
261 String tempBlocklisted = (String) session.getState().get(SPF_TEMPBLOCKLISTED);
262
263
264 if (recipientAddress != null
265 && (recipientAddress.getUser().equalsIgnoreCase("postmaster")
266 || recipientAddress.getUser().equalsIgnoreCase("abuse") || ((session
267 .isAuthRequired() && session.getUser() != null)))) {
268
269
270 return false;
271 } else {
272
273 if ((blocklisted != null && blocklisted.equals("true")) || tempBlocklisted != null) {
274 return true;
275 }
276 }
277 return false;
278 }
279
280
281
282
283 public JunkHandlerData getJunkHandlerData(SMTPSession session) {
284 String blocklisted = (String) session.getState().get(SPF_BLOCKLISTED);
285 String blocklistedDetail = (String) session.getState().get(SPF_DETAIL);
286 String tempBlocklisted = (String) session.getState().get(SPF_TEMPBLOCKLISTED);
287 JunkHandlerData data = new JunkHandlerData();
288
289
290 if (blocklisted != null && blocklisted.equals("true")) {
291 data.setRejectResponseString("530 " + DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.SECURITY_AUTH) + " "
292 + blocklistedDetail);
293 } else if (tempBlocklisted != null
294 && tempBlocklisted.equals("true")) {
295 data.setRejectResponseString("451 " + DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_DIR_SERVER) + " "
296 + "Temporarily rejected: Problem on SPF lookup");
297 }
298 data.setJunkScoreLogString("Not match SPF-Record. Add junkScore: " + getScore());
299 data.setRejectLogString("Not match SPF-Record. Reject email");
300 data.setScoreName("SPFCheck");
301 return data;
302 }
303
304
305
306
307 private class SPFLoggerAdapter implements org.apache.james.jspf.core.Logger {
308
309
310
311
312 private org.apache.avalon.framework.logger.Logger logger;
313
314 public SPFLoggerAdapter(org.apache.avalon.framework.logger.Logger logger) {
315 this.logger = logger;
316 }
317
318
319
320
321 public void debug(String arg0) {
322 logger.debug(arg0);
323 }
324
325
326
327
328 public void debug(String arg0, Throwable arg1) {
329 logger.debug(arg0, arg1);
330 }
331
332
333
334
335 public void error(String arg0) {
336 logger.error(arg0);
337 }
338
339
340
341
342 public void error(String arg0, Throwable arg1) {
343 logger.error(arg0, arg1);
344 }
345
346
347
348
349 public void fatalError(String arg0) {
350 logger.fatalError(arg0);
351 }
352
353
354
355
356 public void fatalError(String arg0, Throwable arg1) {
357 logger.fatalError(arg0, arg1);
358 }
359
360
361
362
363 public void info(String arg0) {
364 logger.info(arg0);
365 }
366
367
368
369
370 public void info(String arg0, Throwable arg1) {
371 logger.info(arg0, arg1);
372 }
373
374
375
376
377 public boolean isDebugEnabled() {
378 return logger.isDebugEnabled();
379 }
380
381
382
383
384 public boolean isErrorEnabled() {
385 return logger.isErrorEnabled();
386 }
387
388
389
390
391 public boolean isFatalErrorEnabled() {
392 return logger.isFatalErrorEnabled();
393 }
394
395
396
397
398 public boolean isInfoEnabled() {
399 return logger.isInfoEnabled();
400 }
401
402
403
404
405 public boolean isWarnEnabled() {
406 return logger.isWarnEnabled();
407 }
408
409
410
411
412 public void warn(String arg0) {
413 logger.warn(arg0);
414 }
415
416
417
418
419 public void warn(String arg0, Throwable arg1) {
420 logger.warn(arg0, arg1);
421 }
422
423
424
425
426 public org.apache.james.jspf.core.Logger getChildLogger(String arg0) {
427 return new SPFLoggerAdapter(logger.getChildLogger(arg0));
428 }
429
430 }
431
432 }