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.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 }