View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
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   * SPFHandler which could be used to reject mail based on spf. The Following attributes
44   * 
45   * org.apache.james.smtpserver.spf.header - Holds the header which can be attached later
46   * 
47   * Sample Configuration: <br>
48   * <br>
49   * &lt;handler class="org.apache.james.smtpserver.core.SPFHandler" command="MAIL,RCPT"&gt;
50   * &lt;blockSoftFail&gt;false&lt;/blockSoftFail&gt;
51   * &lt;checkAuthNetworks&gt;false&lt/checkAuthNetworks&gt; 
52   * &lt;/handler&gt;
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       * If set to true the mail will also be rejected on a softfail
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       * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
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      * @see org.apache.avalon.framework.activity.Initializable#initialize()
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      * block the email on a softfail
122      * 
123      * @param blockSoftFail
124      *            true or false
125      */
126     public void setBlockSoftFail(boolean blockSoftFail) {
127         this.blockSoftFail = blockSoftFail;
128     }
129     
130     /**
131      * block the email on a permerror
132      * 
133      * @param blockPermError
134      *            true or false
135      */
136     public void setBlockPermError(boolean blockPermError) {
137         this.blockPermError = blockPermError;
138     }
139 
140     /**
141      * DNSService to use
142      * 
143      * @param dnsService The DNSService
144      */
145     public void setDNSService(DNSService dnsService) {
146         this.dnsService = dnsService;
147     }
148 
149     /**
150      * Set to true if AuthNetworks should be included in the EHLO check
151      * 
152      * @param checkAuthNetworks
153      *            Set to true to enable
154      */
155     public void setCheckAuthNetworks(boolean checkAuthNetworks) {
156         this.checkAuthNetworks = checkAuthNetworks;
157     }
158 
159     /**
160      * Calls the SPFcheck
161      * 
162      * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
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      * Calls a SPF check
174      * 
175      * @param session
176      *            SMTP session object
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         // We have no Sender or HELO/EHLO yet return false
186         if (sender == null || heloEhlo == null) {
187             getLogger().info("No Sender or HELO/EHLO present");
188         } else {
189             // No checks for authorized cliends
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                 // Store the header
207                 session.getState().put(SPF_HEADER, result.getHeaderText());
208 
209                 getLogger().info(
210                         "Result for " + ip + " - " + sender + " - " + heloEhlo
211                                 + " = " + spfResult);
212 
213                 // Check if we should block!
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      * @see org.apache.james.smtpserver.CommandHandler#getImplCommands()
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      * @see org.apache.james.smtpserver.MessageHandler#onMessage(SMTPSession)
245      */
246     public void onMessage(SMTPSession session) {
247         Mail mail = session.getMail();
248 
249         // Store the spf header as attribute for later using
250         mail.setAttribute(SPF_HEADER_MAIL_ATTRIBUTE_NAME, (String) session.getState().get(SPF_HEADER));
251     }
252     
253 
254     /**
255      * @see org.apache.james.smtpserver.core.filter.fastfail.AbstractJunkHandler#check(org.apache.james.smtpserver.SMTPSession)
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         // Check if the recipient is postmaster or abuse..
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             // Check if session is blocklisted
273             if ((blocklisted != null && blocklisted.equals("true")) || tempBlocklisted != null) {
274                 return true;
275             }
276         }
277         return false;
278     }
279     
280     /**
281      * @see org.apache.james.smtpserver.core.filter.fastfail.AbstractJunkHandler#getJunkHandlerData(org.apache.james.smtpserver.SMTPSession)
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         // Check if session is blocklisted
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      * Inner class to provide a wrapper for loggin to avalon
306      */
307     private class SPFLoggerAdapter implements org.apache.james.jspf.core.Logger {
308 
309         /**
310          * Avalon Logger
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          * @see org.apache.james.jspf.core.Logger#debug(String)
320          */
321         public void debug(String arg0) {
322             logger.debug(arg0);
323         }
324 
325         /**
326          * @see org.apache.james.jspf.core.Logger#debug(String, Throwable)
327          */
328         public void debug(String arg0, Throwable arg1) {
329             logger.debug(arg0, arg1);
330         }
331 
332         /**
333          * @see org.apache.james.jspf.core.Logger#error(String)
334          */
335         public void error(String arg0) {
336             logger.error(arg0);
337         }
338 
339         /**
340          * @see org.apache.james.jspf.core.Logger#error(String, Throwable)
341          */
342         public void error(String arg0, Throwable arg1) {
343             logger.error(arg0, arg1);
344         }
345 
346         /**
347          * @see org.apache.james.jspf.core.Logger#fatalError(String)
348          */
349         public void fatalError(String arg0) {
350             logger.fatalError(arg0);
351         }
352 
353         /**
354          * @see org.apache.james.jspf.core.Logger#fatalError(String, Throwable)
355          */
356         public void fatalError(String arg0, Throwable arg1) {
357             logger.fatalError(arg0, arg1);
358         }
359 
360         /**
361          * @see org.apache.james.jspf.core.Logger#info(String)
362          */
363         public void info(String arg0) {
364             logger.info(arg0);
365         }
366 
367         /**
368          * @see org.apache.james.jspf.core.Logger#info(String, Throwable)
369          */
370         public void info(String arg0, Throwable arg1) {
371             logger.info(arg0, arg1);
372         }
373 
374         /**
375          * @see org.apache.james.jspf.core.Logger#isDebugEnabled()
376          */
377         public boolean isDebugEnabled() {
378             return logger.isDebugEnabled();
379         }
380 
381         /**
382          * @see org.apache.james.jspf.core.Logger#isErrorEnabled()
383          */
384         public boolean isErrorEnabled() {
385             return logger.isErrorEnabled();
386         }
387 
388         /**
389          * @see org.apache.james.jspf.core.Logger#isFatalErrorEnabled()
390          */
391         public boolean isFatalErrorEnabled() {
392             return logger.isFatalErrorEnabled();
393         }
394 
395         /**
396          * @see org.apache.james.jspf.core.Logger#isInfoEnabled()
397          */
398         public boolean isInfoEnabled() {
399             return logger.isInfoEnabled();
400         }
401 
402         /**
403          * @see org.apache.james.jspf.core.Logger#isWarnEnabled()
404          */
405         public boolean isWarnEnabled() {
406             return logger.isWarnEnabled();
407         }
408 
409         /**
410          * @see org.apache.james.jspf.core.Logger#warn(String)
411          */
412         public void warn(String arg0) {
413             logger.warn(arg0);
414         }
415 
416         /**
417          * @see org.apache.james.jspf.core.Logger#warn(String, Throwable)
418          */
419         public void warn(String arg0, Throwable arg1) {
420             logger.warn(arg0, arg1);
421         }
422 
423         /**
424          * @see org.apache.james.jspf.core.Logger#getChildLogger(String)
425          */
426         public org.apache.james.jspf.core.Logger getChildLogger(String arg0) {
427             return new SPFLoggerAdapter(logger.getChildLogger(arg0));
428         }
429 
430     }
431 
432 }