Monday, March 31, 2008

Reference of JavaMail

Looking Up a JavaMail Session

The standard Java Naming and Directory Interface™ (JNDI) subcontext for JavaMail sessions is java:comp/env/mail.

Registering JavaMail sessions in the mail naming subcontext of a JNDI namespace, or in one of its child subcontexts, is standard. The JNDI namespace is hierarchical, like a file system's directory structure, so it is easy to find and nest references. A JavaMail session is bound to a logical JNDI name. The name identifies a subcontext, mail, of the root context, and a logical name. To change the JavaMail session, you can change its entry in the JNDI namespace without having to modify the application.

The resource lookup in the application code looks like this:

InitialContext ic = new InitialContext();
String snName = "java:comp/env/mail/MyMailSession";
Session session = (Session)ic.lookup(snName);

For more information about the JNDI API, see Chapter 13, "Using the Java Naming and Directory Interface."


Sending Messages Using JavaMail

To send a message using JavaMail, perform the following tasks:

  1. Import the packages that you need:

import java.util.*;
import javax.activation.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.naming.*;

  1. Look up the JavaMail session, as described in Looking Up a JavaMail Session:

InitialContext ic = new InitialContext();
String snName = "java:comp/env/mail/MyMailSession";
Session session = (Session)ic.lookup(snName);

  1. Override the JavaMail session properties if necessary. For example:

Properties props = session.getProperties();
props.put("mail.from", "user2@mailserver.com");

  1. Create a MimeMessage. The msgRecipient, msgSubject, and msgTxt variables in the following example contain input from the user:

Message msg = new MimeMessage(session);
msg.setSubject(msgSubject);
msg.setSentDate(new Date());
msg.setFrom();
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(msgRecipient, false));
msg.setText(msgTxt);

  1. Send the message:

Transport.send(msg);


Reading Messages Using JavaMail

To read a message using JavaMail, perform the following tasks:

  1. Import the packages that you need:

import java.util.*;
import javax.activation.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.naming.*;

  1. Look up the JavaMail session, as described in Looking Up a JavaMail Session:

InitialContext ic = new InitialContext();
String snName = "java:comp/env/mail/MyMailSession";
Session session = (javax.mail.Session)ic.lookup(snName);

  1. Override the JavaMail session properties if necessary. For example:

Properties props = session.getProperties();
props.put("mail.from", "user2@mailserver.com");

  1. Get a Store object from the Session, then connect to the mail server using the Store object's connect() method. You must supply a mail server name, a mail user name, and a password.

Store store = session.getStore();
store.connect("MailServer", "MailUser", "secret");

  1. Get the INBOX folder:

Folder folder = store.getFolder("INBOX");

  1. It is efficient to read the Message objects (which represent messages on the server) into an array:

Message[] messages = folder.getMessages();


Secure JavaMail with JSSE

To read a message using JavaMail, perform the following tasks:

  1. Register JSSE providers, you can register them permanently in the <java home>\jre\lib\security\java.security properties file or register them dynamically by calling the Security.addProvider() method in your code:

Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());

  1. Then you need to replace JavaMail's default socket factory with JSSE's SSL socket factory. This approach is similar to the one used in "Java Tip 96: Use HTTPS in Your Java Client Code"; however, in our case, we have no control over socket creation since sockets are created inside the JavaMail framework, not in the application code or storage provider. Fortunately, JavaMail accepts several undocumented properties, allowing you to set up a custom socket factory class and some other parameters. Those properties are:

mail.<protocol>.socketFactory.class
mail.<protocol>.socketFactory.fallback
mail.<protocol>.socketFactory.port
mail.<protocol>.timeout

  1. use the following code to replace a socket factory:

final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
Properties props = System.getProperties();
// IMAP provider
props.setProperty( "mail.imap.socketFactory.class", SSL_FACTORY);
// POP3 provider
props.setProperty( "mail.pop3.socketFactory.class", SSL_FACTORY);
// NNTP provider (if any)
// props.setProperty( "mail.nntp.socketFactory.class", SSL_FACTORY);

  1. If you want JavaMail to use only secure connections, you configure the appropriate properties so JavaMail doesn't fall back to an unsecure connection when a secure one fails:

// IMAP provider
props.setProperty( "mail.imap.socketFactory.fallback", "false");
// POP3 provider
props.setProperty( "mail.pop3.socketFactory.fallback", "false");
// NNTP provider (if any)
// props.setProperty( "mail.nntp.socketFactory.fallback", "false");

  1. then change the default port number to the corresponding port that your protocol's secure version uses; otherwise, you must use a fully qualified address (that includes a port number) in the URL passed to JavaMail (for example, imap://id:password@your.imap.server.com:993/folder/), or else you get an "unrecognized SSL handshake" exception. You specify these properties like so:

// IMAP provider
props.setProperty( "mail.imap.port", "993");
props.setProperty( "mail.imap.socketFactory.port", "993");
// POP3 provider
props.setProperty( "mail.pop3.port", "995");
props.setProperty( "mail.pop3.socketFactory.port", "995");
// NNTP provider (if any)
// props.setProperty( "mail.pop3.port", "563");
// props.setProperty( "mail.pop3.socketFactory.port", "563");

  1. After setting up all the properties, you can open a secure JavaMail session:

Session session = Session.getInstance(props);

 

Secure JavaMail with JSSE

Java Tip 115: Secure JavaMail with JSSE

Add secure, SSL-based connections to JavaMail

The Java Secure Socket Extension (JSSE) API provides SSL functionality that you can add to any JavaMail storage provider. Before we get started, you must download and install JavaMail, JSSE, and all the required packages as described in the JSSE and JavaMail documentation on Sun's Website (most required downloads are included in Java 2 Standard Edition 1.4), as well as JavaMail and JSSE providers of your choice.

Before the JSSE framework can recognize any JSSE providers, you must register them permanently in the <java home>\jre\lib\security\java.security properties file or register them dynamically by calling the Security.addProvider() method in your code:

  Security.addProvider( new com.sun.net.ssl.internal.ssl.Provider());


Then you need to replace JavaMail's default socket factory with JSSE's SSL socket factory. This approach is similar to the one used in "Java Tip 96: Use HTTPS in Your Java Client Code"; however, in our case, we have no control over socket creation since sockets are created inside the JavaMail framework, not in the application code or storage provider. Fortunately, JavaMail accepts several undocumented properties, allowing you to set up a custom socket factory class and some other parameters. Those properties are:

  mail.<protocol>.socketFactory.class
mail.<protocol>.socketFactory.fallback
mail.<protocol>.socketFactory.port
mail.<protocol>.timeout


Now you can use the following code to replace a socket factory:

  final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
Properties props = System.getProperties();
// IMAP provider
props.setProperty( "mail.imap.socketFactory.class", SSL_FACTORY);
// POP3 provider
props.setProperty( "mail.pop3.socketFactory.class", SSL_FACTORY);
// NNTP provider (if any)
// props.setProperty( "mail.nntp.socketFactory.class", SSL_FACTORY);


If you want JavaMail to use only secure connections, you configure the appropriate properties so JavaMail doesn't fall back to an unsecure connection when a secure one fails:

  // IMAP provider
props.setProperty( "mail.imap.socketFactory.fallback", "false");
// POP3 provider
props.setProperty( "mail.pop3.socketFactory.fallback", "false");
// NNTP provider (if any)
// props.setProperty( "mail.nntp.socketFactory.fallback", "false");


You then change the default port number to the corresponding port that your protocol's secure version uses; otherwise, you must use a fully qualified address (that includes a port number) in the URL passed to JavaMail (for example, imap://id:password@your.imap.server.com:993/folder/), or else you get an "unrecognized SSL handshake" exception. You specify these properties like so:

  // IMAP provider
props.setProperty( "mail.imap.port", "993");
props.setProperty( "mail.imap.socketFactory.port", "993");
// POP3 provider
props.setProperty( "mail.pop3.port", "995");
props.setProperty( "mail.pop3.socketFactory.port", "995");
// NNTP provider (if any)
// props.setProperty( "mail.pop3.port", "563");
// props.setProperty( "mail.pop3.socketFactory.port", "563");


After setting up all the properties, you can open a secure JavaMail session:

  Session session = Session.getInstance(props);


Certificates

Unfortunately, you may realize that the code above throws an SSLException "untrusted server cert chain" if the mail server certificate is not installed locally. In that case, you should obtain a correct, valid certificate for the server and use the keytool utility to add it to a local key storage at <javahome>\jre\lib\security\cacerts.

Alternatively, you can replace the default TrustManager. However, since you can't control socket creation and you want your TrustManager to work, you must use your own socket factory with SSLSocketFactory, which creates secure sockets.

Our simple example TrustManager implementation, DummyTrustManager.java, accepts all certificates without validation, even if the certificate name differs from the server name. This could be a security issue you may want to address in your application. For example, you can show a nice GUI dialog asking if the user trusts the certificate, and then store the certificate locally (you've probably seen these dialogs in Netscape Navigator or MS Internet Explorer).

Here is the code:

  import com.sun.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
public class DummyTrustManager implements X509TrustManager {
public boolean isClientTrusted( X509Certificate[] cert) {
return true;
}
public boolean isServerTrusted( X509Certificate[] cert) {
return true;
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[ 0];
}
}


You also need to implement a custom SSLSocketFactory, like DummySSLSocketFactor.java. We will use the Proxy pattern to change the TrustManager of a standard SSLSocketFactory:

  import com.sun.net.ssl.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
public class DummySSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory factory;
public DummySSLSocketFactory() {
System.out.println( "DummySocketFactory instantiated");
try {
SSLContext sslcontext = SSLContext.getInstance( "TLS");
sslcontext.init( null, // No KeyManager required
new TrustManager[] { new DummyTrustManager()},
new java.security.SecureRandom());
factory = ( SSLSocketFactory) sslcontext.getSocketFactory();
} catch( Exception ex) {
ex.printStackTrace();
}
}
public static SocketFactory getDefault() {
return new DummySSLSocketFactory();
}
public Socket createSocket( Socket socket, String s, int i, boolean
flag)
throws IOException {
return factory.createSocket( socket, s, i, flag);
}
public Socket createSocket( InetAddress inaddr, int i,
InetAddress inaddr1, int j) throws IOException {
return factory.createSocket( inaddr, i, inaddr1, j);
}
public Socket createSocket( InetAddress inaddr, int i) throws
IOException {
return factory.createSocket( inaddr, i);
}
public Socket createSocket( String s, int i, InetAddress inaddr, int j)
throws IOException {
return factory.createSocket( s, i, inaddr, j);
}
public Socket createSocket( String s, int i) throws IOException {
return factory.createSocket( s, i);
}
public String[] getDefaultCipherSuites() {
return factory.getSupportedCipherSuites();
}
public String[] getSupportedCipherSuites() {
return factory.getSupportedCipherSuites();
}
}


Finally you should install the SocketFactory provider like this:

  Security.setProperty( "ssl.SocketFactory.provider", 
"DummySSLSocketFactory");


You should execute this code before creating any JavaMail sessions. Also, don't forget to make the DummySSLSocketFactory class visible to your code.

Joining technologies

We've created a sample application, called FolderList.java, based on the JavaMail mail folder listing demo, which uses the above code. It shows that you can securely connect to the mail server using JSSE by configuring the JavaMail framework properly. With this approach, you're ready to make your JavaMail-based application support secure communication without any significant changes in your own code.

Monday, March 3, 2008

Google's Engineering Philosophy


A slide from a presentation at last year's Google Engineering Open House listed 12 principles that guide programming at Google:

1. All developers work out of a ~single source depot; shared infrastructure!
2. A developer can fix bugs anywhere in the source tree.
3. Building a product takes 3 commands ("get, config, make")
4. Uniform coding style guidelines across company
5. Code reviews mandatory for all checkins
6. Pervasive unit testing, written by developers
7. Unit tests run continuously, email sent on failure
8. Powerful tools, shared company-wide
9. Rapid project cycles; developers change projects often; 20% time
10. Peer-driven review process; flat management structure
11. Transparency into projects, code, process, ideas, etc.
12. Dozens of offices around world => hire best people regardless of location
Google+