/* * Copyright (C) 2005-2008 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.openfire.plugin.emailListener; import com.sun.mail.imap.IMAPFolder; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.Log; import org.jivesoftware.util.StringUtils; import org.xmpp.packet.JID; import javax.mail.*; import javax.mail.event.MessageCountAdapter; import javax.mail.event.MessageCountEvent; import java.security.Security; import java.util.Date; import java.util.Properties; import java.util.Collection; import java.util.ArrayList; /** * Email listener service that will send an instant message to specified users * when new email messages are found. * * @author Gaston Dombiak */ public class EmailListener { private static final String SSL_FACTORY = "org.jivesoftware.util.SimpleSSLSocketFactory"; private static final EmailListener instance = new EmailListener(); /** * Message listener that will process new emails found in the IMAP server. */ private MessageCountAdapter messageListener; private Folder folder; private boolean started = false; public static EmailListener getInstance() { return instance; } private EmailListener() { } /** * Returns true if a connection to the IMAP server was successful. * * @param host Host to connect to. * @param port Port to connect over. * @param isSSLEnabled True if an SSL connection will be attempted. * @param user Username to use for authentication. * @param password Password to use for authentication. * @param folderName Folder to check. * @return true if a connection to the IMAP server was successful. */ public static boolean testConnection(String host, int port, boolean isSSLEnabled, String user, String password, String folderName) { Folder folder = openFolder(host, port, isSSLEnabled, user, password, folderName); boolean success = folder != null && folder.isOpen(); closeFolder(folder, null); return success; } /** * Opens a connection to the IMAP server and listen for new messages. */ public void start() { // Check that the listner service is not running if (started) { return; } Thread thread = new Thread("Email Listener Thread") { @Override public void run() { // Open the email folder and keep it folder = openFolder(getHost(), getPort(), isSSLEnabled(), getUser(), getPassword(), getFolder()); if (folder != null) { // Listen for new email messages until #stop is requested listenMessages(); } } }; thread.setDaemon(true); thread.start(); started = true; } /** * Closes the active connection to the IMAP server. */ public void stop() { closeFolder(folder, messageListener); started = false; folder = null; messageListener = null; } private void listenMessages() { try { // Add messageCountListener to listen for new messages messageListener = new MessageCountAdapter() { @Override public void messagesAdded(MessageCountEvent ev) { Message[] msgs = ev.getMessages(); // Send new messages to specified users for (Message msg : msgs) { try { sendMessage(msg); } catch (Exception e) { Log.error("Error while sending new email message", e); } } } }; folder.addMessageCountListener(messageListener); // Check mail once in "freq" MILLIseconds int freq = getFrequency(); boolean supportsIdle = false; try { if (folder instanceof IMAPFolder) { IMAPFolder f = (IMAPFolder) folder; f.idle(); supportsIdle = true; } } catch (FolderClosedException fex) { throw fex; } catch (MessagingException mex) { supportsIdle = false; } while (messageListener != null) { if (supportsIdle && folder instanceof IMAPFolder) { IMAPFolder f = (IMAPFolder) folder; f.idle(); } else { Thread.sleep(freq); // sleep for freq milliseconds // This is to force the IMAP server to send us // EXISTS notifications. if (folder != null && folder.isOpen()) { folder.getMessageCount(); } } } } catch (Exception ex) { Log.error("Error listening new email messages", ex); } } private void sendMessage(Message message) throws Exception { StringBuilder sb = new StringBuilder(); sb.append("New email has been received\n"); // FROM sb.append("From: "); for (Address address : message.getFrom()) { sb.append(address.toString()).append(" "); } sb.append("\n"); // DATE Date date = message.getSentDate(); sb.append("Received: ").append(date != null ? date.toString() : "UNKNOWN").append("\n"); // SUBJECT sb.append("Subject: ").append(message.getSubject()).append("\n"); // Apend body appendMessagePart(message, sb); // Send notifications to specified users for (String user : getUsers()) { // Create notification message org.xmpp.packet.Message notification = new org.xmpp.packet.Message(); notification.setFrom(XMPPServer.getInstance().getServerInfo().getXMPPDomain()); notification.setTo(user); notification.setSubject("New email has been received"); notification.setBody(sb.toString()); // Send notification message XMPPServer.getInstance().getMessageRouter().route(notification); } } private void appendMessagePart(Part part, StringBuilder sb) throws Exception { /* * Using isMimeType to determine the content type avoids * fetching the actual content data until we need it. */ if (part.isMimeType("text/plain")) { // This is plain text" sb.append((String) part.getContent()).append("\n"); } else if (part.isMimeType("multipart/*")) { // This is a Multipart Multipart mp = (Multipart) part.getContent(); int count = mp.getCount(); for (int i = 0; i < count; i++) { appendMessagePart(mp.getBodyPart(i), sb); } } else if (part.isMimeType("message/rfc822")) { // This is a Nested Message appendMessagePart((Part) part.getContent(), sb); } else { /* * If we actually want to see the data, and it's not a * MIME type we know, fetch it and check its Java type. */ /*Object o = part.getContent(); if (o instanceof String) { // This is a string System.out.println((String) o); } else if (o instanceof InputStream) { // This is just an input stream InputStream is = (InputStream) o; int c; while ((c = is.read()) != -1) { System.out.write(c); } } else { // This is an unknown type System.out.println(o.toString()); }*/ } } private static Folder openFolder(String host, Integer port, Boolean isSSLEnabled, String user, String password, String folder) { if (host == null || port == null || isSSLEnabled == null || user == null || password == null || folder == null) { return null; } try { Properties props = System.getProperties(); props.setProperty("mail.imap.host", host); props.setProperty("mail.imap.port", String.valueOf(port)); props.setProperty("mail.imap.connectiontimeout", String.valueOf(10 * 1000)); // Allow messages with a mix of valid and invalid recipients to still be sent. props.setProperty("mail.debug", JiveGlobals.getProperty("plugin.email.listener.debug", "false")); // Methology from an article on www.javaworld.com (Java Tip 115) // We will attempt to failback to an insecure connection // if the secure one cannot be made if (isSSLEnabled) { // Register with security provider. Security.setProperty("ssl.SocketFactory.provider", SSL_FACTORY); //props.setProperty("mail.imap.starttls.enable", "true"); props.setProperty("mail.imap.socketFactory.class", SSL_FACTORY); props.setProperty("mail.imap.socketFactory.fallback", "true"); } // Get a Session object Session session = Session.getInstance(props, null); // Get a Store object Store store = session.getStore(isSSLEnabled ? "imaps" : "imap"); // Connect store.connect(host, user, password); // Open a Folder Folder newFolder = store.getFolder(folder); if (newFolder == null || !newFolder.exists()) { Log.error("Invalid email folder: " + folder); return null; } newFolder.open(Folder.READ_WRITE); return newFolder; } catch (Exception e) { Log.error("Error while initializing email listener", e); } return null; } private static void closeFolder(Folder folder, MessageCountAdapter messageListener) { if (folder != null) { if (messageListener != null) { folder.removeMessageCountListener(messageListener); } try { folder.close(false); } catch (MessagingException e) { Log.error("Error closing folder", e); } } } /** * Returns the host where the IMAP server is running or <tt>null</tt> if none was defined. * * @return the host where the IMAP server is running or null if none was defined. */ public String getHost() { return JiveGlobals.getProperty("plugin.email.listener.host"); } /** * Sets the host where the IMAP server is running or <tt>null</tt> if none was defined. * * @param host the host where the IMAP server is running or null if none was defined. */ public void setHost(String host) { JiveGlobals.setProperty("plugin.email.listener.host", host); } /** * Returns the port where the IMAP server is listening. By default unsecured connections * use port 143 and secured ones use 993. * * @return port where the IMAP server is listening. */ public int getPort() { return JiveGlobals.getIntProperty("plugin.email.listener.port", isSSLEnabled() ? 993 : 143); } /** * Sets the port where the IMAP server is listening. By default unsecured connections * use port 143 and secured ones use 993. * * @param port port where the IMAP server is listening. */ public void setPort(int port) { JiveGlobals.setProperty("plugin.email.listener.port", Integer.toString(port)); } /** * Returns the user to use to connect to the IMAP server. A null value means that * this property needs to be configured to be used. * * @return the user to use to connect to the IMAP server. */ public String getUser() { return JiveGlobals.getProperty("plugin.email.listener.user"); } /** * Sets the user to use to connect to the IMAP server. A null value means that * this property needs to be configured to be used. * * @param user the user to use to connect to the IMAP server. */ public void setUser(String user) { JiveGlobals.setProperty("plugin.email.listener.user", user); } /** * Returns the password to use to connect to the IMAP server. A null value means that * this property needs to be configured to be used. * * @return the password to use to connect to the IMAP server. */ public String getPassword() { return JiveGlobals.getProperty("plugin.email.listener.password"); } /** * Sets the password to use to connect to the IMAP server. A null value means that * this property needs to be configured to be used. * * @param password the password to use to connect to the IMAP server. */ public void setPassword(String password) { JiveGlobals.setProperty("plugin.email.listener.password", password); } /** * Returns the name of the folder. In some Stores, name can be an absolute path if * it starts with the hierarchy delimiter. Else it is interpreted relative to the * 'root' of this namespace. * * @return the name of the folder. */ public String getFolder() { return JiveGlobals.getProperty("plugin.email.listener.folder"); } /** * Sets the name of the folder. In some Stores, name can be an absolute path if * it starts with the hierarchy delimiter. Else it is interpreted relative to the * 'root' of this namespace. * * @param folder the name of the folder. */ public void setFolder(String folder) { JiveGlobals.setProperty("plugin.email.listener.folder", folder); } /** * Returns the milliseconds to wait to check for new emails. This frequency * is used if the IMAP server does not support idle. * * @return the milliseconds to wait to check for new emails. */ public int getFrequency() { return JiveGlobals.getIntProperty("plugin.email.listener.frequency", 5 * 60 * 1000); } /** * Sets the milliseconds to wait to check for new emails. This frequency * is used if the IMAP server does not support idle. * * @param frequency the milliseconds to wait to check for new emails. */ public void setFrequency(int frequency) { JiveGlobals.setProperty("plugin.email.listener.frequency", Integer.toString(frequency)); } /** * Returns true if SSL is enabled to connect to the server. * * @return true if SSL is enabled to connect to the server. */ public boolean isSSLEnabled() { return JiveGlobals.getBooleanProperty("plugin.email.listener.ssl", false); } /** * Sets if SSL is enabled to connect to the server. * * @param enabled true if SSL is enabled to connect to the server. */ public void setSSLEnabled(boolean enabled) { JiveGlobals.setProperty("plugin.email.listener.ssl", Boolean.toString(enabled)); } public Collection<String> getUsers() { String users = JiveGlobals.getProperty("plugin.email.listener.users"); if (users == null || users.trim().length() == 0) { Collection<String> admins = new ArrayList<String>(); for (JID jid : XMPPServer.getInstance().getAdmins()) { admins.add(jid.toString()); } return admins; } return StringUtils.stringToCollection(users); } public void setUsers(Collection<String> users) { JiveGlobals.setProperty("plugin.email.listener.users", StringUtils.collectionToString(users)); } }