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 * <handler class="org.apache.james.smtpserver.SpamAssassinHandler">
49 * <spamdHost>localhost</spamdHost>
50 * <spamdPort>783</spamdPort> <br>
51 * <spamdRejectionHits>15.0</spamdRejectionHits>
52 * <checkAuthNetworks>false</checkAuthNetworks> </handler>
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 }