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.util.scanner;
22
23 import javax.mail.MessagingException;
24 import javax.mail.internet.MimeMessage;
25 import java.net.Socket;
26 import java.net.UnknownHostException;
27 import java.io.BufferedReader;
28 import java.io.IOException;
29 import java.io.InputStreamReader;
30 import java.io.OutputStream;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.StringTokenizer;
34
35 /**
36 * Sends the message through daemonized SpamAssassin (spamd), visit <a
37 * href="SpamAssassin.org">SpamAssassin.org</a> for info on configuration.
38 */
39 public class SpamAssassinInvoker {
40
41 /**
42 * The mail attribute under which the status get stored
43 */
44 public final static String STATUS_MAIL_ATTRIBUTE_NAME = "org.apache.james.spamassassin.status";
45
46 /**
47 * The mail attribute under which the flag get stored
48 */
49 public final static String FLAG_MAIL_ATTRIBUTE_NAME = "org.apache.james.spamassassin.flag";
50
51 private String spamdHost;
52
53 private int spamdPort;
54
55 private String hits = "?";
56
57 private String required = "?";
58
59 private HashMap headers = new HashMap();
60
61 /**
62 * Init the spamassassin invoker
63 *
64 * @param spamdHost The host on which spamd runs
65 * @param spamdPort The port on which spamd listen
66 */
67 public SpamAssassinInvoker(String spamdHost, int spamdPort) {
68 this.spamdHost = spamdHost;
69 this.spamdPort = spamdPort;
70 }
71
72 /**
73 * Scan a MimeMessage for spam by passing it to spamd.
74 *
75 * @param message
76 * The MimeMessage to scan
77 * @return true if spam otherwise false
78 * @throws MessagingException
79 * if an error on scanning is detected
80 */
81 public boolean scanMail(MimeMessage message) throws MessagingException {
82 Socket socket = null;
83 OutputStream out = null;
84 BufferedReader in = null;
85
86 try {
87 socket = new Socket(spamdHost, spamdPort);
88
89 out = socket.getOutputStream();
90 in = new BufferedReader(new InputStreamReader(socket
91 .getInputStream()));
92 out.write("CHECK SPAMC/1.2\r\n\r\n".getBytes());
93
94 // pass the message to spamd
95 message.writeTo(out);
96 out.flush();
97 socket.shutdownOutput();
98 String s = null;
99 while ((s = in.readLine()) != null) {
100 if (s.startsWith("Spam:")) {
101 StringTokenizer t = new StringTokenizer(s, " ");
102 boolean spam;
103 try {
104 t.nextToken();
105 spam = Boolean.valueOf(t.nextToken()).booleanValue();
106 } catch (Exception e) {
107 // On exception return flase
108 return false;
109 }
110 t.nextToken();
111 hits = t.nextToken();
112 t.nextToken();
113 required = t.nextToken();
114
115 if (spam) {
116 // message was spam
117 headers.put(FLAG_MAIL_ATTRIBUTE_NAME, "YES");
118 headers.put(STATUS_MAIL_ATTRIBUTE_NAME,
119 new StringBuffer("Yes, hits=").append(hits)
120 .append(" required=").append(required)
121 .toString());
122
123 // spam detected
124 return true;
125 } else {
126 // add headers
127 headers.put(FLAG_MAIL_ATTRIBUTE_NAME, "NO");
128 headers.put(STATUS_MAIL_ATTRIBUTE_NAME,
129 new StringBuffer("No, hits=").append(hits)
130 .append(" required=").append(required)
131 .toString());
132
133 return false;
134 }
135 }
136 }
137 return false;
138 } catch (UnknownHostException e1) {
139 throw new MessagingException(
140 "Error communicating with spamd. Unknown host: "
141 + spamdHost);
142 } catch (IOException e1) {
143 throw new MessagingException("Error communicating with spamd on "
144 + spamdHost + ":" + spamdPort + " Exception: " + e1);
145 } catch (MessagingException e1) {
146 throw new MessagingException("Error communicating with spamd on "
147 + spamdHost + ":" + spamdPort + " Exception: " + e1);
148 } finally {
149 try {
150 in.close();
151 out.close();
152 socket.close();
153 } catch (Exception e) {
154 // Should never happin
155 }
156
157 }
158 }
159
160 /**
161 * Return the hits which was returned by spamd
162 *
163 * @return hits The hits which was detected
164 */
165 public String getHits() {
166 return hits;
167 }
168
169 /**
170 * Return the required hits
171 *
172 * @return required The required hits before a message is handled as spam
173 */
174 public String getRequiredHits() {
175 return required;
176 }
177
178 /**
179 * Return the headers as attributes which spamd generates
180 *
181 * @return headers Map of headers to add as attributes
182 */
183 public Map getHeadersAsAttribute() {
184 return headers;
185 }
186 }