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  package org.apache.james.smtpserver.core.filter.fastfail;
22  
23  import java.util.Iterator;
24  
25  import javax.mail.MessagingException;
26  import javax.mail.internet.MimeMessage;
27  
28  import org.apache.avalon.framework.configuration.Configurable;
29  import org.apache.avalon.framework.configuration.Configuration;
30  import org.apache.avalon.framework.configuration.ConfigurationException;
31  import org.apache.avalon.framework.logger.AbstractLogEnabled;
32  import org.apache.james.dsn.DSNStatus;
33  import org.apache.james.smtpserver.MessageHandler;
34  import org.apache.james.smtpserver.SMTPSession;
35  import org.apache.james.util.scanner.SpamAssassinInvoker;
36  import org.apache.mailet.Mail;
37  
38  /**
39   * This MessageHandler could be used to check message against spamd before
40   * accept the email. So its possible to reject a message on smtplevel if a
41   * configured hits amount is reached. The handler add the follow attributes to
42   * the mail object:<br>
43   * org.apache.james.spamassassin.status - Holds the status
44   * org.apache.james.spamassassin.flag - Holds the flag <br>
45   * 
46   * Sample Configuration: <br>
47   * <br>
48   * &lt;handler class="org.apache.james.smtpserver.SpamAssassinHandler"&gt;
49   * &lt;spamdHost&gt;localhost&lt;/spamdHost&gt;
50   * &lt;spamdPort&gt;783&lt;/spamdPort&gt; <br>
51   * &lt;spamdRejectionHits&gt;15.0&lt;/spamdRejectionHits&gt;
52   * &lt;checkAuthNetworks&gt;false&lt;/checkAuthNetworks&gt; &lt;/handler&gt;
53   */
54  public class SpamAssassinHandler extends AbstractLogEnabled implements
55          MessageHandler, Configurable {
56  
57      /**
58       * The port spamd is listen on
59       */
60      private int spamdPort = 783;
61  
62      /**
63       * The host spamd is runnin on
64       */
65      private String spamdHost = "localhost";
66  
67      /**
68       * The hits on which the message get rejected
69       */
70      private double spamdRejectionHits = 0.0;
71  
72      private boolean checkAuthNetworks = false;
73  
74      /**
75       * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
76       */
77      public void configure(Configuration arg0) throws ConfigurationException {
78          Configuration spamdHostConf = arg0.getChild("spamdHost", false);
79          if (spamdHostConf != null) {
80              setSpamdHost(spamdHostConf.getValue("localhost"));
81          }
82  
83          Configuration spamdPortConf = arg0.getChild("spamdPort", false);
84          if (spamdPortConf != null) {
85              setSpamdPort(spamdPortConf.getValueAsInteger(783));
86          }
87  
88          Configuration spamdRejectionHitsConf = arg0.getChild(
89                  "spamdRejectionHits", false);
90          if (spamdRejectionHitsConf != null) {
91              setSpamdRejectionHits(spamdRejectionHitsConf.getValueAsDouble(0.0));
92          }
93  
94          Configuration configRelay = arg0.getChild("checkAuthNetworks", false);
95          if (configRelay != null) {
96              setCheckAuthNetworks(configRelay.getValueAsBoolean(false));
97          }
98  
99      }
100 
101     /**
102      * Set to true if AuthNetworks should be included in the EHLO check
103      * 
104      * @param checkAuthNetworks
105      *            Set to true to enable
106      */
107     public void setCheckAuthNetworks(boolean checkAuthNetworks) {
108         this.checkAuthNetworks = checkAuthNetworks;
109     }
110 
111     /**
112      * Set the host the spamd daemon is running at
113      * 
114      * @param spamdHost
115      *            The spamdHost
116      */
117     public void setSpamdHost(String spamdHost) {
118         this.spamdHost = spamdHost;
119     }
120 
121     /**
122      * Set the port the spamd damon is listen on
123      * 
124      * @param spamdPort
125      *            the spamdPort
126      */
127     public void setSpamdPort(int spamdPort) {
128         this.spamdPort = spamdPort;
129     }
130 
131     /**
132      * Set the hits on which the message will be rejected.
133      * 
134      * @param spamdRejectionHits
135      *            The hits
136      */
137     public void setSpamdRejectionHits(double spamdRejectionHits) {
138         this.spamdRejectionHits = spamdRejectionHits;
139 
140     }
141 
142     /**
143      * @see org.apache.james.smtpserver.MessageHandler#onMessage(SMTPSession)
144      */
145     public void onMessage(SMTPSession session) {
146 
147         // Not scan the message if relaying allowed
148         if (session.isRelayingAllowed() && !checkAuthNetworks) {
149             return;
150         }
151 
152         try {
153             Mail mail = session.getMail();
154             MimeMessage message = mail.getMessage();
155             SpamAssassinInvoker sa = new SpamAssassinInvoker(spamdHost,
156                     spamdPort);
157             sa.scanMail(message);
158 
159             Iterator headers = sa.getHeadersAsAttribute().keySet().iterator();
160 
161             // Add the headers
162             while (headers.hasNext()) {
163                 String key = headers.next().toString();
164 
165                 mail.setAttribute(key, (String) sa.getHeadersAsAttribute().get(
166                         key));
167             }
168 
169             // Check if rejectionHits was configured
170             if (spamdRejectionHits > 0) {
171                 try {
172                     double hits = Double.parseDouble(sa.getHits());
173 
174                     // if the hits are bigger the rejectionHits reject the
175                     // message
176                     if (spamdRejectionHits <= hits) {
177                         String responseString = "554 "
178                                 + DSNStatus.getStatus(DSNStatus.PERMANENT,
179                                         DSNStatus.SECURITY_OTHER)
180                                 + " This message reach the spam hits treshold. Please contact the Postmaster if the email is not SPAM. Message rejected";
181                         StringBuffer buffer = new StringBuffer(256).append(
182                                 "Rejected message from ").append(
183                                 session.getState().get(SMTPSession.SENDER)
184                                         .toString()).append(" from host ")
185                                 .append(session.getRemoteHost()).append(" (")
186                                 .append(session.getRemoteIPAddress()).append(
187                                         ") " + responseString).append(
188                                         ". Required rejection hits: "
189                                                 + spamdRejectionHits
190                                                 + " hits: " + hits);
191                         getLogger().info(buffer.toString());
192                         session.writeResponse(responseString);
193 
194                         // Message reject .. abort it!
195                         session.abortMessage();
196                     }
197                 } catch (NumberFormatException e) {
198                     // hits unknown
199                 }
200             }
201         } catch (MessagingException e) {
202             getLogger().error(e.getMessage());
203         }
204 
205     }
206 }