001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.util;
021:
022: import java.util.Date;
023: import java.util.Properties;
024:
025: import javax.mail.*;
026: import javax.mail.internet.AddressException;
027: import javax.mail.internet.InternetAddress;
028: import javax.mail.internet.MimeMessage;
029: import javax.naming.Context;
030: import javax.naming.InitialContext;
031: import javax.naming.NamingException;
032:
033: import org.apache.log4j.Logger;
034:
035: import com.ecyrd.jspwiki.TextUtil;
036: import com.ecyrd.jspwiki.WikiEngine;
037:
038: /**
039: * <p>Contains static methods for sending e-mails to recipients using JNDI-supplied
040: * <a href="http://java.sun.com/products/javamail/">JavaMail</a>
041: * Sessions supplied by a web container (preferred) or configured via
042: * <code>jspwiki.properties</code>; both methods are described below.
043: * Because most e-mail servers require authentication,
044: * for security reasons implementors are <em>strongly</em> encouraged to use
045: * container-managed JavaMail Sessions so that passwords are not exposed in
046: * <code>jspwiki.properties</code>.</p>
047: * <p>To enable e-mail functions within JSPWiki, administrators must do three things:
048: * ensure that the required JavaMail JARs are on the runtime classpath, configure
049: * JavaMail appropriately, and (recommdended) configure the JNDI JavaMail session factory.</p>
050: * <strong>JavaMail runtime JARs</strong>
051: * <p>The first step is easy: JSPWiki bundles
052: * recent versions of the required JavaMail <code>mail.jar</code> and
053: * <code>activation.jar</code> into the JSPWiki WAR file; so, out of the box
054: * this is already taken care of. However, when using JNDI-supplied
055: * Session factories, these should be moved, <em>not copied</em>, to a classpath location
056: * where the JARs can be shared by both the JSPWiki webapp and the container. For example,
057: * Tomcat 5 provides the directory <code><var>$CATALINA_HOME></var>/common/lib</code>
058: * for storage of shared JARs; move <code>mail.jar</code> and <code>activation</code>
059: * there instead of keeping them in <code>/WEB-INF/lib</code>.</p>
060: * <strong>JavaMail configuration</strong>
061: * <p>Regardless of the method used for supplying JavaMail sessions (JNDI container-managed
062: * or via <code>jspwiki.properties</code>, JavaMail needs certain properties
063: * set in order to work correctly. Configurable properties are these:</p>
064: * <table border="1">
065: * <tr>
066: * <thead>
067: * <th>Property</th>
068: * <th>Default</th>
069: * <th>Definition</th>
070: * <thead>
071: * </tr>
072: * <tr>
073: * <td><code>jspwiki.mail.jndiname</code></td>
074: * <td><code>mail/Session</code></td>
075: * <td>The JNDI name of the JavaMail session factory</td>
076: * </tr>
077: * <tr>
078: * <td><code>mail.smtp.host</code></td>
079: * <td><code>127.0.0.1</code></td>
080: * <td>The SMTP mail server from which messages will be sent.</td>
081: * </tr>
082: * <tr>
083: * <td><code>mail.smtp.port</code></td>
084: * <td><code>25</code></td>
085: * <td>The port number of the SMTP mail service.</td>
086: * </tr>
087: * <tr>
088: * <td><code>mail.smtp.account</code></td>
089: * <td>(not set)</td>
090: * <td>The user name of the sender. If this value is supplied, the JavaMail
091: * session will attempt to authenticate to the mail server before sending
092: * the message. If not supplied, JavaMail will attempt to send the message
093: * without authenticating (i.e., it will use the server as an open relay).
094: * In real-world scenarios, you should set this value.</td>
095: * </tr>
096: * <tr>
097: * <td><code>mail.smtp.password</code></td>
098: * <td>(not set)</td>
099: * <td>The password of the sender. In real-world scenarios, you
100: * should set this value.</td>
101: * </tr>
102: * <tr>
103: * <td><code>mail.from</code></td>
104: * <td><code><var>${user.name}</var>@<var>${mail.smtp.host}</var>*</code></td>
105: * <td>The e-mail address of the sender.</td>
106: * </tr>
107: * <tr>
108: * <td><code>mail.smtp.timeout</code></td>
109: * <td><code>5000*</code></td>
110: * <td>Socket I/O timeout value, in milliseconds. The default is 5 seconds.</td>
111: * </tr>
112: * <tr>
113: * <td><code>mail.smtp.connectiontimeout</code></td>
114: * <td><code>5000*</code></td>
115: * <td>Socket connection timeout value, in milliseconds. The default is 5 seconds.</td>
116: * </tr>
117: * <tr>
118: * <td><code>mail.smtp.starttls.enable</code></td>
119: * <td><code>true*</code></td>
120: * <td>If true, enables the use of the STARTTLS command (if
121: * supported by the server) to switch the connection to a
122: * TLS-protected connection before issuing any login commands.
123: * Note that an appropriate trust store must configured so that
124: * the client will trust the server's certificate. By default,
125: * the JRE trust store contains root CAs for most public certificate
126: * authorities.</td>
127: * </tr>
128: * </table>
129: * <p>*These defaults apply only if the stand-alone Session factory is used
130: * (that is, these values are obtained from <code>jspwiki.properties</code>).
131: * If using a container-managed JNDI Session factory, the container will
132: * likely supply its own default values, and you should probably override
133: * them (see the next section).</p>
134: * <strong>Container JNDI Session factory configuration</strong>
135: * <p>You are strongly encouraged to use a container-managed JNDI factory for
136: * JavaMail sessions, rather than configuring JavaMail through <code>jspwiki.properties</code>.
137: * To do this, you need to two things: uncomment the <code><resource-ref></code> block
138: * in <code>/WEB-INF/web.xml</code> that enables container-managed JavaMail, and
139: * configure your container's JavaMail resource factory. The <code>web.xml</code>
140: * part is easy: just uncomment the section that looks like this:</p>
141: * <pre><resource-ref>
142: * <description>Resource reference to a container-managed JNDI JavaMail factory for sending e-mails.</description>
143: * <res-ref-name>mail/Session</res-ref-name>
144: * <res-type>javax.mail.Session</res-type>
145: * <res-auth>Container</res-auth>
146: * </resource-ref></pre>
147: * <p>To configure your container's resource factory, follow the directions supplied by
148: * your container's documentation. For example, the
149: * <a href="http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html#JavaMail%20Sessions">Tomcat
150: * 5.5 docs</a> state that you need a properly configured <code><Resource></code>
151: * element inside the JSPWiki webapp's <code><Context></code> declaration. Here's an example shows
152: * how to do it:</p>
153: * <pre><Context ...>
154: * ...
155: * <Resource name="mail/Session" auth="Container"
156: * type="javax.mail.Session"
157: * mail.smtp.host="127.0.0.1"/>
158: * mail.smtp.port="25"/>
159: * mail.smtp.account="your-account-name"/>
160: * mail.smtp.password="your-password"/>
161: * mail.from="Snoop Dogg <snoop@dogg.org>"/>
162: * mail.smtp.timeout="5000"/>
163: * mail.smtp.connectiontimeout="5000"/>
164: * mail.smtp.starttls.enable="true"/>
165: * ...
166: * </Context></pre>
167: * <p>Note that with Tomcat (and most other application containers) you can also declare the JavaMail
168: * JNDI factory as a global resource, shared by all applications, instead of as a local JSPWiki
169: * resource as we have done here. For example, the following entry in
170: * <code><var>$CATALINA_HOME</var>/conf/server.xml</code> creates a global resource:</p>
171: * <pre><GlobalNamingResources>
172: * <Resource name="mail/Session" auth="Container"
173: * type="javax.mail.Session"
174: * ...
175: * mail.smtp.starttls.enable="true"/>
176: * </GlobalNamingResources></pre>
177: * <p>This approach — creating a global JNDI resource — yields somewhat decreased
178: * deployment complexity because the JSPWiki webapp no longer needs its own JavaMail resource
179: * declaration. However, it is slightly less secure because it means that all other applications
180: * can now obtain a JavaMail session if they want to. In many cases, this <em>is</em> what
181: * you want.</p>
182: * <p>NOTE: Versions of Tomcat 5.5 later than 5.5.17, and up to and including 5.5.23 have a
183: * b0rked version of <code><var>$CATALINA_HOME</var>/common/lib/naming-factory.jar</code>
184: * that prevents usage of JNDI. To avoid this problem, you should patch your 5.5.23 version
185: * of <code>naming-factory.jar</code> with the one from 5.5.17. This is a known issue
186: * and the bug report (#40668) is
187: * <a href="http://issues.apache.org/bugzilla/show_bug.cgi?id=40668">here</a>.
188: *
189: * @author Christoph Sauer
190: * @author Dan Frankowski
191: * @author Andrew Jaquith
192: */
193: public final class MailUtil {
194: private static final String JAVA_COMP_ENV = "java:comp/env";
195:
196: private static final String FALSE = "false";
197:
198: private static final String TRUE = "true";
199:
200: private static boolean c_useJndi = true;
201:
202: public static final String PROP_MAIL_AUTH = "mail.smtp.auth";
203:
204: protected static final Logger log = Logger
205: .getLogger(MailUtil.class);
206:
207: protected static final String DEFAULT_MAIL_JNDI_NAME = "mail/Session";
208:
209: protected static final String DEFAULT_MAIL_HOST = "localhost";
210:
211: protected static final String DEFAULT_MAIL_PORT = "25";
212:
213: protected static final String DEFAULT_MAIL_TIMEOUT = "5000";
214:
215: protected static final String DEFAULT_SENDER = "jspwiki@localhost";
216:
217: protected static final String PROP_MAIL_JNDI_NAME = "jspwiki.mail.jndiname";
218:
219: protected static final String PROP_MAIL_HOST = "mail.smtp.host";
220:
221: protected static final String PROP_MAIL_PORT = "mail.smtp.port";
222:
223: protected static final String PROP_MAIL_ACCOUNT = "mail.smtp.account";
224:
225: protected static final String PROP_MAIL_PASSWORD = "mail.smtp.password";
226:
227: protected static final String PROP_MAIL_TIMEOUT = "mail.smtp.timeout";
228:
229: protected static final String PROP_MAIL_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout";
230:
231: protected static final String PROP_MAIL_TRANSPORT = "smtp";
232:
233: protected static final String PROP_MAIL_SENDER = "mail.from";
234:
235: protected static final String PROP_MAIL_STARTTLS = "mail.smtp.starttls.enable";
236:
237: /**
238: * Private constructor prevents instantiation.
239: */
240: private MailUtil() {
241: }
242:
243: /**
244: * <p>Sends an e-mail to a specified receiver using a JavaMail Session supplied
245: * by a JNDI mail session factory (preferred) or a locally initialized
246: * session based on properties in <code>jspwiki.properties</code>.
247: * See the top-level JavaDoc for this class for a description of
248: * required properties and their default values.</p>
249: * <p>The e-mail address used for the <code>to</code> parameter must be in
250: * RFC822 format, as described in the JavaDoc for {@link javax.mail.internet.InternetAddress}
251: * and more fully at
252: * <a href="http://www.freesoft.org/CIE/RFC/822/index.htm">http://www.freesoft.org/CIE/RFC/822/index.htm</a>.
253: * In other words, e-mail addresses should look like this:</p>
254: * <blockquote><code>Snoop Dog <snoop.dog@shizzle.net><br/>
255: * snoop.dog@shizzle.net</code></blockquote>
256: * <p>Note that the first form allows a "friendly" user name to be supplied
257: * in addition to the actual e-mail address.</p>
258: *
259: * @param engine the WikiEngine for the current wiki
260: * @param to the receiver
261: * @param subject the subject line of the message
262: * @param content the contents of the mail message, as plain text
263: */
264: public static void sendMessage(WikiEngine engine, String to,
265: String subject, String content) throws AddressException,
266: MessagingException {
267: String from = engine.getWikiProperties().getProperty(
268: PROP_MAIL_SENDER, DEFAULT_SENDER).trim();
269: sendMessage(engine, to, from, subject, content);
270: }
271:
272: /**
273: * <p>Sends an e-mail to a specified receiver from a specified sender, using a
274: * JavaMail Session supplied by a JNDI mail session factory (preferred) or
275: * a locally initialized session based on properties in
276: * <code>jspwiki.properties</code>. See the top-level JavaDoc for this
277: * class for a description of required properties and their
278: * default values.</p>
279: * <p>The e-mail addresses used for the <code>to</code> and <code>from</code>
280: * parameters must be in RFC822 format, as described in the JavaDoc for
281: * {@link javax.mail.internet.InternetAddress} and more fully at
282: * <a href="http://www.freesoft.org/CIE/RFC/822/index.htm">http://www.freesoft.org/CIE/RFC/822/index.htm</a>.
283: * In other words, e-mail addresses should look like this:</p>
284: * <blockquote><code>Snoop Dog <snoop.dog@shizzle.net><br/>
285: * snoop.dog@shizzle.net</code></blockquote>
286: * <p>Note that the first form allows a "friendly" user name to be supplied
287: * in addition to the actual e-mail address.</p>
288: *
289: * @param engine the WikiEngine for the current wiki
290: * @param to the receiver
291: * @param from the address the email will be from
292: * @param subject the subject line of the message
293: * @param content the contents of the mail message, as plain text
294: */
295: public static void sendMessage(WikiEngine engine, String to,
296: String from, String subject, String content)
297: throws MessagingException {
298: Properties props = engine.getWikiProperties();
299: String jndiName = props.getProperty(PROP_MAIL_JNDI_NAME,
300: DEFAULT_MAIL_JNDI_NAME).trim();
301: Session session = null;
302:
303: if (c_useJndi) {
304: // Try getting the Session from the JNDI factory first
305: try {
306: session = getJNDIMailSession(jndiName);
307: c_useJndi = false;
308: } catch (NamingException e) {
309: // Oops! JNDI factory must not be set up
310: }
311: }
312:
313: // JNDI failed; so, get the Session from the standalone factory
314: if (session == null) {
315: session = getStandaloneMailSession(props);
316: }
317:
318: try {
319: // Create and address the message
320: MimeMessage msg = new MimeMessage(session);
321: msg.setFrom(new InternetAddress(from));
322: msg.setRecipients(Message.RecipientType.TO, InternetAddress
323: .parse(to, false));
324: msg.setSubject(subject);
325: msg.setText(content, "UTF-8");
326: msg.setSentDate(new Date());
327:
328: // Send and log it
329: Transport.send(msg);
330: if (log.isInfoEnabled()) {
331: log.info("Sent e-mail to=" + to + ", subject=\""
332: + subject + "\", jndi="
333: + (c_useJndi ? TRUE : FALSE));
334: }
335: } catch (MessagingException e) {
336: log.error(e);
337: throw e;
338: }
339: }
340:
341: // --------- JavaMail Session Helper methods ---------------------------------
342:
343: /**
344: * Returns a stand-alone JavaMail Session by looking up the correct
345: * mail account, password and host from a supplied set of properties.
346: * If the JavaMail property {@value #PROP_MAIL_ACCOUNT} is set to
347: * a value that is non-<code>null</code> and of non-zero length, the
348: * Session will be initialized with an instance of
349: * {@link javax.mail.Authenticator}.
350: * @param props the properties that contain mail session properties
351: * @return the initialized JavaMail Session
352: */
353: protected static Session getStandaloneMailSession(Properties props) {
354: // Read the JSPWiki settings from the properties
355: String host = props.getProperty(PROP_MAIL_HOST,
356: DEFAULT_MAIL_HOST);
357: String port = props.getProperty(PROP_MAIL_PORT,
358: DEFAULT_MAIL_PORT);
359: String account = props.getProperty(PROP_MAIL_ACCOUNT);
360: String password = props.getProperty(PROP_MAIL_PASSWORD);
361: boolean starttls = TextUtil.getBooleanProperty(props,
362: PROP_MAIL_STARTTLS, true);
363:
364: boolean useAuthentication = account != null
365: && account.length() > 0;
366:
367: Properties mailProps = new Properties();
368:
369: // Set JavaMail properties
370: mailProps.put(PROP_MAIL_HOST, host);
371: mailProps.put(PROP_MAIL_PORT, port);
372: mailProps.put(PROP_MAIL_TIMEOUT, DEFAULT_MAIL_TIMEOUT);
373: mailProps.put(PROP_MAIL_CONNECTION_TIMEOUT,
374: DEFAULT_MAIL_TIMEOUT);
375: mailProps.put(PROP_MAIL_STARTTLS, starttls ? TRUE : FALSE);
376:
377: // Add SMTP authentication if required
378: Session session = null;
379: if (useAuthentication) {
380: mailProps.put(PROP_MAIL_AUTH, TRUE);
381: SmtpAuthenticator auth = new SmtpAuthenticator(account,
382: password);
383:
384: session = Session.getInstance(mailProps, auth);
385: } else {
386: session = Session.getInstance(mailProps);
387: }
388:
389: if (log.isDebugEnabled()) {
390: String mailServer = host + ":" + port + ", auth="
391: + (useAuthentication ? TRUE : FALSE);
392: log
393: .debug("JavaMail session obtained from standalone mail factory: "
394: + mailServer);
395: }
396: return session;
397: }
398:
399: /**
400: * Returns a JavaMail Session instance from a JNDI container-managed factory.
401: * @param jndiName the JNDI name for the resource. If <code>null</code>, the default value
402: * of <code>mail/Session</code> will be used
403: * @return the initialized JavaMail Session
404: * @throws NamingException if the Session cannot be obtained; for example, if the factory is not configured
405: */
406: protected static Session getJNDIMailSession(String jndiName)
407: throws NamingException {
408: Session session = null;
409: try {
410: Context initCtx = new InitialContext();
411: Context ctx = (Context) initCtx.lookup(JAVA_COMP_ENV);
412: session = (Session) ctx.lookup(jndiName);
413: } catch (NamingException e) {
414: log
415: .warn("JavaMail initialization error: "
416: + e.getMessage());
417: throw e;
418: }
419: if (log.isDebugEnabled()) {
420: log
421: .debug("JavaMail session obtained from JNDI mail factory: "
422: + jndiName);
423: }
424: return session;
425: }
426:
427: /**
428: * Simple {@link javax.mail.Authenticator} subclass that authenticates a user to
429: * an SMTP server.
430: * @author Christoph Sauer
431: */
432: protected static class SmtpAuthenticator extends Authenticator {
433:
434: private static final String BLANK = "";
435: private final String m_pass;
436: private final String m_login;
437:
438: /**
439: * Constructs a new SmtpAuthenticator with a supplied username and password.
440: * @param login the user name
441: * @param pass the password
442: */
443: public SmtpAuthenticator(String login, String pass) {
444: super ();
445: m_login = login == null ? BLANK : login;
446: m_pass = pass == null ? BLANK : pass;
447: }
448:
449: /**
450: * Returns the password used to authenticate to the SMTP server.
451: */
452: public PasswordAuthentication getPasswordAuthentication() {
453: if (BLANK.equals(m_pass)) {
454: return null;
455: }
456:
457: return new PasswordAuthentication(m_login, m_pass);
458: }
459:
460: }
461:
462: }
|