View Javadoc

1   /*
2    * Copyright (c) 2004-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.mercury.imap;
14  
15  
16  import java.io.EOFException;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.io.InterruptedIOException;
20  import java.net.Socket;
21  import java.util.concurrent.Executor;
22  
23  import javax.mail.Session;
24  import javax.mail.Store;
25  
26  import org.abstracthorizon.danube.connection.Connection;
27  import org.abstracthorizon.danube.connection.ConnectionException;
28  import org.abstracthorizon.danube.connection.ConnectionHandler;
29  import org.abstracthorizon.mercury.common.StorageManager;
30  import org.abstracthorizon.mercury.common.command.CommandException;
31  import org.abstracthorizon.mercury.imap.cmd.IMAPCommandFactory;
32  import org.abstracthorizon.mercury.imap.cmd.UIDCommand;
33  import org.abstracthorizon.mercury.imap.response.BADResponse;
34  import org.abstracthorizon.mercury.imap.response.ByeResponse;
35  import org.abstracthorizon.mercury.imap.response.NOResponse;
36  import org.abstracthorizon.mercury.imap.response.OKResponse;
37  import org.abstracthorizon.mercury.imap.response.Response;
38  import org.abstracthorizon.mercury.imap.util.IMAPScanner;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * A class for IMAP service
44   *
45   * @author Daniel Sendula
46   */
47  public class IMAPConnectionHandler implements ConnectionHandler {
48  
49      /** Logger */
50      protected final Logger logger = LoggerFactory.getLogger(getClass());
51  
52      /** Storage manager */
53      protected StorageManager storageManager;
54  
55      /** Selected store */
56      protected Store store;
57  
58      /** Command factory */
59      protected IMAPCommandFactory factory = new IMAPCommandFactory();
60  
61      /** Session */
62      protected Session session;
63  
64      /** Thread pool */
65      protected Executor threadPool;
66  
67      /** Keystore pass phrase */
68      protected char[] passPhrase;
69  
70      /** Allow insecure connections */
71      protected boolean allowInsecure = true;
72  
73      /**
74       * Constructor
75       */
76      public IMAPConnectionHandler() {
77      }
78  
79      /**
80       * Sets storage manager
81       * @param storageManager storage manager
82       */
83      public void setStorageManager(StorageManager storageManager) {
84          this.storageManager = storageManager;
85      }
86  
87      /**
88       * Returns storage manager
89       * @return storage manager
90       */
91      public StorageManager getStorageManager() {
92          return storageManager;
93      }
94  
95      /**
96       * Returns command factory
97       * @return command factory
98       */
99      public IMAPCommandFactory getFactory() {
100         return factory;
101     }
102 
103     /**
104      * Returns javamail session that is used
105      * @return javamail session that is used
106      */
107     public Session getJavaMailSession() {
108         return session;
109     }
110 
111     /**
112      * Sets javamail session that to be used
113      * @param session javamail session that to be used
114      */
115     public void setJavaMailSession(Session session) {
116         this.session = session;
117     }
118 
119     /**
120      * Sets thread pool to be used for parallel tasks
121      * @param executor thread pool
122      */
123     public void setThreadPool(Executor executor) {
124         this.threadPool = executor;
125     }
126 
127     /**
128      * Returns thread pool to be used for parallel tasks
129      * @return thread pool to be used for parallel tasks
130      */
131     public Executor getThreadPool() {
132         return threadPool;
133     }
134 
135     /**
136      * Returns keystore as an input stream
137      * @return keystore as an input stream
138      */
139     public InputStream getKeyStoreInputStream() {
140         return Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/keystore");
141     }
142 
143     /**
144      * Sets pass phrase of a keystore to be used for switching to TLS
145      * @param passPhrase password of a keystore
146      */
147     public void setPassPhrase(char[] passPhrase) {
148         this.passPhrase = passPhrase;
149     }
150 
151     /**
152      * Returns pass phrase of a keystore to be used for switching to TLS
153      * @return pass phrase of a keystore to be used for switching to TLS
154      */
155     public char[] getPassPhrase() {
156         return passPhrase;
157     }
158 
159     /**
160      * Returns if insecure connections are allowed
161      * @return if insecure connections are allowed
162      */
163     public boolean isInsecureAllowed() {
164         return allowInsecure;
165     }
166 
167     /**
168      * Sets if insecure connections are allowed
169      * @param allowInsecure is insecure connection allowed
170      */
171     public void setInsecureAllowed(boolean allowInsecure) {
172         this.allowInsecure = allowInsecure;
173     }
174 
175     /**
176      * Handles IMAP connection
177      * @param connection connection
178      */
179     public void handleConnection(Connection connection) {
180         IMAPSession imapConnection = new IMAPSession(connection, this);
181 
182         try {
183             try {
184                 try {
185                     new OKResponse(imapConnection, Response.UNTAGGED_RESPONSE, "Service Ready").submit();
186                     boolean persistConnection = true;
187                     while (persistConnection) {
188                         IMAPScanner scanner = imapConnection.getScanner();
189 
190                         processInput(imapConnection);
191 
192                         Socket socket = (Socket)connection.adapt(Socket.class);
193                         persistConnection = (socket != null) && !socket.isInputShutdown() && !socket.isOutputShutdown();
194 
195                         if (((socket == null) || socket.isConnected()) && !imapConnection.isCleanInput()) {
196                             scanner.skip_line();
197                             imapConnection.setCleanInput(true);
198                         }
199                     }
200                 } catch (ConnectionException e) {
201                     Throwable w = e.getCause();
202                     if (w != null) {
203                         throw w;
204                     }
205                 }
206             } catch (InterruptedIOException e) {
207                 imapConnection.setKeepLog(true);
208                 imapConnection.writeLogMessage("Closing because of inactivity");
209                 new ByeResponse(imapConnection); // ???
210             } catch (EOFException e) {
211                 // Don't long sudden and proper stream closes.
212             } catch (IOException e) {
213                 Socket socket = (Socket)connection.adapt(Socket.class);
214                 if ((socket != null) && socket.isConnected()) {
215                     logger.error("End of session exception: ", e);
216                 }
217             } catch (Throwable t) {
218                 logger.error("Got problem: ", t);
219             }
220         } finally {
221             imapConnection.close();
222         }
223     }
224 
225     /**
226      * Processes input
227      * @param imapConnection imap connection
228      * @throws IOException
229      */
230     public void processInput(IMAPSession imapConnection) throws IOException {
231         imapConnection.setCleanInput(false);
232 
233         IMAPScanner scanner = imapConnection.getScanner();
234 
235         StringBuffer tagBuffer = new StringBuffer();
236         try {
237             if (!scanner.tag(tagBuffer)) {
238                 new BADResponse(imapConnection, Response.UNTAGGED_RESPONSE, "tag is missing").submit();
239             }
240         } catch (IOException e) {
241             e.printStackTrace();
242             // we don't care if someone just closes connection before tag read read.
243             imapConnection.close();
244             return;
245         }
246         String tag = tagBuffer.toString();
247         imapConnection.setTag(tag);
248         if (!scanner.is_char(' ')) {
249             new BADResponse(imapConnection, "space after tag is missing").submit();
250         }
251         if (!imapConnection.isAuthorised()) {
252             // Not Authenticated state
253             if (commandNonAuth(imapConnection)) {
254             } else if (commandAny(imapConnection)) {
255             } else {
256                 new BADResponse(imapConnection, "Command not recognised or not allowed in non-authorised mode.").submit();
257             }
258         } else if (imapConnection.getSelectedFolder() == null) {
259             // Authenticated state
260             if (commandAuth(imapConnection)){
261             } else if (commandAny(imapConnection)) {
262             } else {
263                 new BADResponse(imapConnection, "Command not recognised or not allowed in not selected mode.").submit();
264             }
265         } else {
266             // Selected state
267             if (commandSelected(imapConnection)) {
268             } else if (commandAuth(imapConnection)) {
269             } else if (commandAny(imapConnection)) {
270             } else {
271                 new BADResponse(imapConnection, "Command not recognised or not allowed in non-authorised mode.").submit();
272             }
273         }
274     }
275 
276     /**
277      * Invokes command
278      * @param imapConnection imap connection
279      * @param name comamnd name
280      * @throws IOException
281      */
282     public void invokeCommand(IMAPSession imapConnection, String name) throws IOException {
283         invokeCommand(imapConnection, name, false);
284     }
285 
286     /**
287      * Invokes command
288      * @param imapConnection imap connection
289      * @param name command name
290      * @param uid is UID function
291      * @throws IOException
292      */
293     public void invokeCommand(IMAPSession imapConnection, String name, boolean uid) throws IOException {
294         IMAPScanner scanner = imapConnection.getScanner();
295         if (scanner.is_char(' ') || scanner.peek_char('\r')) {
296             try {
297                 ConnectionHandler command = factory.getCommand(name);
298                 // command.init(imapConnection);
299                 if (uid && (command instanceof UIDCommand)) {
300                     ((UIDCommand)command).setAsUID();
301                 }
302                 command.handleConnection(imapConnection);
303             } catch (NOCommandException e) {
304                 logger.debug("NO: ", e);
305                 new NOResponse(imapConnection, name+" "+e.getMessage()).submit();
306                 imapConnection.setKeepLog(true);
307             } catch (BADCommandException e) {
308                 logger.error("BAD: ", e);
309                 new BADResponse(imapConnection, name+" "+e.getMessage()).submit();
310                 imapConnection.setKeepLog(true);
311             } catch (CommandException e) {
312                 logger.error("UNEXPECTED: ", e);
313                 new BADResponse(imapConnection, e.getMessage()).submit();
314                 imapConnection.setKeepLog(true);
315             }
316         } else {
317             new BADResponse(imapConnection, "Missing space after the command").submit();
318         }
319     }
320 
321     /**
322      * Processes any command
323      * @param imapConnection imap connection
324      * @return command is recognised and processes
325      * @throws IOException
326      */
327     public boolean commandAny(IMAPSession imapConnection) throws IOException {
328         IMAPScanner scanner = imapConnection.getScanner();
329         if (scanner.keyword("CAPABILITY")) {
330             invokeCommand(imapConnection, "CAPABILITY");
331             return true;
332         } else if (scanner.keyword("LOGOUT")) {
333             invokeCommand(imapConnection, "LOGOUT");
334             return true;
335         } else if (scanner.keyword("NOOP")) {
336             invokeCommand(imapConnection, "NOOP");
337             return true;
338         } else {
339             return false;
340         }
341     }
342 
343     /**
344      * Processes non-authorised comamnds
345      * @param imapConnection imap connection
346      * @return command is found and executed
347      * @throws IOException
348      */
349     public boolean commandNonAuth(IMAPSession imapConnection) throws IOException {
350         IMAPScanner scanner = imapConnection.getScanner();
351         if (scanner.keyword("LOGIN")) {
352             invokeCommand(imapConnection, "LOGIN");
353         } else if (scanner.keyword("AUTHENTICATE")) {
354             invokeCommand(imapConnection, "AUTHENTICATE");
355         } else if (scanner.keyword("STARTTLS")) {
356             invokeCommand(imapConnection, "STARTTLS");
357         } else {
358             return false;
359         }
360         return true;
361     }
362 
363     /**
364      * Processes authorised commands
365      * @param imapConnection imap connection
366      * @return command is found and executed
367      * @throws IOException
368      */
369     public boolean commandAuth(IMAPSession imapConnection) throws IOException {
370         IMAPScanner scanner = imapConnection.getScanner();
371         if (scanner.keyword("APPEND")) {
372             invokeCommand(imapConnection, "APPEND");
373         } else if (scanner.keyword("CREATE")) {
374             invokeCommand(imapConnection, "CREATE");
375         } else if (scanner.keyword("DELETE")) {
376             invokeCommand(imapConnection, "DELETE");
377         } else if (scanner.keyword("EXAMINE")) {
378             invokeCommand(imapConnection, "EXAMINE");
379         } else if (scanner.keyword("LIST")) {
380             invokeCommand(imapConnection, "LIST");
381         } else if (scanner.keyword("LSUB")) {
382             invokeCommand(imapConnection, "LSUB");
383         } else if (scanner.keyword("RENAME")) {
384             invokeCommand(imapConnection, "RENAME");
385         } else if (scanner.keyword("SELECT")) {
386             invokeCommand(imapConnection, "SELECT");
387         } else if (scanner.keyword("STATUS")) {
388             invokeCommand(imapConnection, "STATUS");
389         } else if (scanner.keyword("SUBSCRIBE")) {
390             invokeCommand(imapConnection, "SUBSCRIBE");
391         } else if (scanner.keyword("UNSUBSCRIBE")) {
392             invokeCommand(imapConnection, "UNSUBSCRIBE");
393         } else {
394             return false;
395         }
396         return true;
397     }
398 
399     /**
400      * Processes select command
401      * @param imapConnection imap connection
402      * @return command is found and processed
403      * @throws IOException
404      */
405     public boolean commandSelected(IMAPSession imapConnection) throws IOException {
406         IMAPScanner scanner = imapConnection.getScanner();
407         if (scanner.keyword("CHECK")) {
408             invokeCommand(imapConnection, "CHECK");
409         } else if (scanner.keyword("CLOSE")) {
410             invokeCommand(imapConnection, "CLOSE");
411         } else if (scanner.keyword("EXPUNGE")) {
412             invokeCommand(imapConnection, "EXPUNGE");
413         } else if (scanner.keyword("COPY")) {
414             invokeCommand(imapConnection, "COPY");
415         } else if (scanner.keyword("FETCH")) {
416             invokeCommand(imapConnection, "FETCH");
417         } else if (scanner.keyword("STORE")) {
418             invokeCommand(imapConnection, "STORE");
419         } else if (scanner.keyword("UID")) {
420             invokeCommand(imapConnection, "UID");
421         } else if (scanner.keyword("SEARCH")) {
422             invokeCommand(imapConnection, "SEARCH");
423         } else if (scanner.keyword("IDLE")) {
424             invokeCommand(imapConnection, "IDLE");
425         } else {
426             return false;
427         }
428         return true;
429     }
430 }