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  import java.io.BufferedInputStream;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  import java.io.PrintStream;
20  import java.io.Reader;
21  import java.io.Writer;
22  import java.net.Socket;
23  import java.util.Properties;
24  
25  import javax.mail.Folder;
26  import javax.mail.MessagingException;
27  import javax.mail.Session;
28  import javax.mail.Store;
29  import javax.mail.event.MessageCountEvent;
30  import javax.mail.event.MessageCountListener;
31  import javax.net.ssl.SSLSocket;
32  import javax.net.ssl.SSLSocketFactory;
33  import javax.security.auth.login.LoginContext;
34  import javax.security.auth.login.LoginException;
35  
36  import org.abstracthorizon.danube.connection.Connection;
37  import org.abstracthorizon.danube.connection.ConnectionWrapper;
38  import org.abstracthorizon.danube.support.logging.LoggingConnection;
39  import org.abstracthorizon.mercury.common.util.SSLUtil;
40  import org.abstracthorizon.mercury.imap.response.ExistsResponse;
41  import org.abstracthorizon.mercury.imap.response.RecentResponse;
42  import org.abstracthorizon.mercury.imap.util.IMAPScanner;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  
47  /**
48   * A class that represents IMAP session. This is connection wrapper.
49   *
50   * @author Daniel Sendula
51   */
52  public class IMAPSession extends ConnectionWrapper implements MessageCountListener {
53  
54      /** Logger */
55      protected static final Logger logger = LoggerFactory.getLogger(IMAPSession.class);
56  
57      /** Handler that created this session */
58      protected IMAPConnectionHandler parent;
59  
60      /** Current tag */
61      protected String tag;
62  
63      /** Current command line */
64      protected String commandLine;
65  
66      /** Is user authenticated and authorised to use some commands */
67      protected boolean authorised = false;
68  
69      /** Output writer */
70      protected Writer writer;
71  
72      /** Input reader */
73      protected Reader reader;
74  
75      /** IMAP scanner to be used */
76      protected IMAPScanner scanner;
77  
78      /** Mail store */
79      protected Store store;
80  
81      /** Selected folder */
82      protected Folder selectedFolder = null;
83  
84      /** Mail session */
85      protected Session session;
86  
87      /** Login context */
88      protected LoginContext lc;
89  
90      /** Is secure conneciton */
91      protected boolean secure = false;
92  
93      /** Are we in IDLE mode */
94      protected boolean idling = false;
95  
96      /**
97       * Clear flag is set to indicate that skip_line mustn't be called after command.
98       * This was needed for STARTTLS command.
99       */
100     protected boolean cleanInput = false;
101 
102     /** Cached socket */
103     protected Socket socket;
104 
105     /** Timestamp command started */
106     protected long commandStarted;
107 
108     /** Default domain if not specified in username */
109     protected String defaultDomain;
110 
111     /**
112      * Constructor
113      * @param connection connection
114      * @param parent imap connection handler that is creating this object
115      */
116     public IMAPSession(Connection connection, IMAPConnectionHandler parent) {
117         super(connection);
118         this.parent = parent;
119         InputStream inputStream = (InputStream)connection.adapt(InputStream.class);
120         OutputStream outputStream = (OutputStream)connection.adapt(OutputStream.class);
121         BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
122         scanner = new IMAPScanner(bufferedInputStream, outputStream);
123 
124         Properties props = new Properties();
125         // We don't want strict headers parsing!
126         props.setProperty("mail.mime.address.strict", "false");
127         session = Session.getInstance(props);
128     }
129 
130     /**
131      * Returns imap connection handler that created this object
132      * @return imap connection handler that created this object
133      */
134     public IMAPConnectionHandler getParent() {
135         return parent;
136     }
137 
138     /**
139      * Sets should log be kept or not
140      * @param keepLog should log be kept
141      */
142     public void setKeepLog(boolean keepLog) {
143         LoggingConnection loggingConnection = (LoggingConnection)connection.adapt(LoggingConnection.class);
144         if (loggingConnection != null) {
145             loggingConnection.setTemporaryLog(!keepLog);
146         }
147     }
148 
149     /**
150      * Returns should log be kept or not
151      * @return should log be kept or not
152      */
153     public boolean getKeepLog() {
154         LoggingConnection loggingConnection = (LoggingConnection)connection.adapt(LoggingConnection.class);
155         if (loggingConnection != null) {
156             return !loggingConnection.isTermporaryLog();
157         } else {
158             return false;
159         }
160     }
161 
162     /**
163      * Writes log message
164      * @param msg log message
165      */
166     public void writeLogMessage(String msg) {
167         LoggingConnection loggingConnection = (LoggingConnection)connection.adapt(LoggingConnection.class);
168         if (loggingConnection != null) {
169             PrintStream out = new PrintStream(loggingConnection.getDebugOutputStream());
170             out.println(msg);
171             out.flush();
172         }
173     }
174 
175     /**
176      * Returns debug output stream
177      * @return debug output stream
178      */
179     public OutputStream getDebugStream() {
180         LoggingConnection loggingConnection = (LoggingConnection)connection.adapt(LoggingConnection.class);
181         if (loggingConnection != null) {
182             return loggingConnection.getDebugOutputStream();
183         } else {
184             return null;
185         }
186     }
187 
188     /**
189      * Is session authorised or not
190      * @return
191      */
192     public boolean isAuthorised() {
193         return authorised;
194     }
195 
196     /**
197      * Returns number of millis how long last command execution lasted.
198      * @return difference of time between now and when last command has been started
199      */
200     public long getCommandLasted() {
201         return System.currentTimeMillis() - commandStarted;
202     }
203 
204     /**
205      * Marks when command has started
206      */
207     public void markCommandStarted() {
208         commandStarted = System.currentTimeMillis();
209     }
210 
211     /**
212      * Returns JavaMail store
213      * @return JavaMail store
214      */
215     public Store getStore() {
216         return store;
217     }
218 
219     /**
220      * Returns selected Folder
221      * @return selected Folder
222      */
223     public Folder getSelectedFolder() {
224         return selectedFolder;
225     }
226 
227     /**
228      * Sets selected Folder
229      * @param folder selected Folder
230      */
231     public void setSelectedFolder(Folder folder) {
232         if (folder != null) {
233             logger.debug("Already selected folder "+folder.getName());
234             folder.removeMessageCountListener(this);
235         }
236         selectedFolder = folder;
237         if (folder != null) {
238             selectedFolder.addMessageCountListener(this);
239         }
240     }
241 
242     /**
243      * IMAP scanner
244      * @return IMAP scanner
245      */
246     public IMAPScanner getScanner() {
247             return scanner;
248     }
249 
250     /**
251      * Returns JavaMail session
252      * @return JavaMail session
253      */
254     public Session getJavaMailSession() {
255         return session;
256     }
257 
258     /**
259      * Sets JavaMail session
260      * @param session JavaMail session
261      */
262     public void setJavaMailSession(Session session) {
263         this.session = session;
264     }
265 
266     /**
267      * Returns if insecure is allowed
268      * @return if insecure is allowed
269      */
270     public boolean isInsecureAllowed() {
271         return parent.isInsecureAllowed();
272     }
273 
274     /**
275      * Returns if socket is SSL socket if socket available. Otherwise <code>true</code>
276      * @return if socket is SSL socket
277      */
278     public boolean isSecure() {
279         Socket socket = (Socket)adapt(Socket.class);
280         if (socket != null) {
281             return socket instanceof SSLSocket;
282         } else {
283             return true;
284         }
285     }
286 
287     /**
288      * Sets if IDLE command is allowed
289      * @param idling if IDLE command is allowed
290      */
291     public synchronized void setIdling(boolean idling) {
292         this.idling = idling;
293     }
294 
295     /**
296      * Returns if IDLE command is allowed
297      * @return if IDLE command is allowed
298      */
299     public boolean isIdling() {
300         return idling;
301     }
302 
303     /**
304      * Returns if it is clean input
305      * @return if it is clean input
306      */
307     public boolean isCleanInput() {
308         return cleanInput;
309     }
310 
311     /**
312      * Sets if it is clean input
313      * @param cleanInput is clean input
314      */
315     public void setCleanInput(boolean cleanInput) {
316         this.cleanInput = cleanInput;
317     }
318 
319     /**
320      * Returns current tag
321      * @return current tag
322      */
323     public String getTag() {
324         return tag;
325     }
326 
327     /**
328      * Sets current tag
329      * @param tag current tag
330      */
331     public void setTag(String tag) {
332         this.tag = tag;
333     }
334 
335     /**
336      * Returns default domain name
337      * @return default domain name
338      */
339     public String getDefaultDomain() {
340         return defaultDomain;
341     }
342 
343     /**
344      * Sets default domain name to be used with usernames
345      * @param domain
346      */
347     public void setDefaultDomain(String domain) {
348         this.defaultDomain = domain;
349     }
350 
351 
352     /**
353      * Authorises session. It uses storage manager for it. If username doesn't contain
354      * domain then it uses default domain name as set in {@link #setDefaultDomain(String)}
355      * @param user username
356      * @param pass password
357      * @return <code>true</code> if it succeded
358      * @throws MessagingException
359      */
360     public boolean authorise(String user, String pass) throws MessagingException {
361         String domain;
362         int i = user.indexOf('@');
363         if (i >= 1) {
364             domain = user.substring(i + 1);
365             user = user.substring(0, i);
366         } else if (defaultDomain != null) {
367             domain = defaultDomain;
368         } else {
369             domain = parent.getStorageManager().getMainDomain();
370         }
371 
372 
373         if (domain != null) {
374             logger.info("AUTHORISE: user " + user + " for domain " + domain);
375         } else {
376             logger.info("AUTHORISE: user " + user + " without domain");
377         }
378 
379 
380         try {
381             store = parent.getStorageManager().findStore(user, domain, pass.toCharArray());
382             authorised = true;
383         } catch (Exception e) {
384             logger.error("Failed to find a store", e);
385         }
386 
387         return authorised;
388     }
389 
390     /**
391      * Removes authorisation
392      */
393     public void unauthorise() {
394         authorised = false;
395         if (lc != null) {
396             try {
397                 lc.logout();
398             } catch (LoginException ignore) {
399             }
400         }
401     }
402 
403     /**
404      * Closes IMAP session (connection)
405      */
406     public void close() {
407         if ((selectedFolder != null) && (selectedFolder.isOpen())) {
408             try {
409                 selectedFolder.removeMessageCountListener(this);
410                 if (selectedFolder.isOpen()) {
411                     selectedFolder.close(false);
412                 }
413             } catch (IllegalStateException ignore) {
414             } catch (MessagingException e1) {
415                 // we don't care!
416                 logger.debug("Forced close on folder excetion ", e1);
417             }
418         }
419         super.close();
420         logger.debug("Finished session ");
421     }
422 
423     /**
424      * Switches to TLS (SSL) socket
425      * @throws IOException
426      */
427     public void switchToTLS() throws IOException {
428         char[] passPhrase = parent.getPassPhrase();
429         InputStream keyStoreInputStream = parent.getKeyStoreInputStream();
430         SSLSocketFactory factory = SSLUtil.getSocketFactory(passPhrase, keyStoreInputStream);
431         if (factory == null) {
432             throw new IOException("Cannot obtain SSLSocket Factory");
433         }
434 
435         // TODO - this makes little sense!!!
436         socket = factory.createSocket(socket, socket.getInetAddress().getHostName(), socket.getPort(), true);
437         ((SSLSocket)socket).setUseClientMode(false);
438     }
439 
440     /**
441      * Notifies that new message is added
442      * @param event message count event
443      */
444     public synchronized void messagesAdded(MessageCountEvent event) {
445         if ((selectedFolder != null) && idling) {
446             parent.getThreadPool().execute(new Runnable(){
447                 public void run() {
448                     try {
449                         new RecentResponse(IMAPSession.this, selectedFolder).submit();
450                         new ExistsResponse(IMAPSession.this, selectedFolder).submit();
451                     } catch (IOException ignore) {
452                     } catch (MessagingException ignore) {
453                     }
454                 }
455             });
456         }
457     }
458 
459     /**
460      * NOtifies that message is removed
461      * @param even event
462      */
463     public void messagesRemoved(MessageCountEvent event) {
464         messagesAdded(event);
465     }
466 }