Accueil / Articles PiApplications. / La plate-forme Java / Exemples de codes source Java

Envoi d'un message via SMTP, STARTTLS ou submission.

La librairie JavaMail permet l'envoi de messages via le protocole SMTP et ses versions sécurisées. On trouve sur Internet de nombreux exemples qui permettent l'envoi de messages en clair via le protocole SMTP. En revanche, on trouve peu d'exemples et encore moins d'exemples à jour qui traitent de l'envoi de messages via un protocole sécurisé. Cet article fournit un exemple de code compatible avec la librairie javamail 1.5.2.

Rappel sur la messagerie "sortante" (SMTP).

Avant ceci, il nous apparait utile de préciser comment on est passé du protocole initial SMTP à des protocoles plus sécurisés. Pour envoyer des courriels, le "client" doit se connecter à un "serveur". Le dialogue entre le client et le serveur doit répondre à un protocole particulier appelé SMTP (cf. RFC 5321 de 2008). Les serveurs SMTP sont identifiés aisément grâce aux champs MX des DNS. L'adresse courriel elle-même comporte deux parties :

  1. un identifiant de boîtes aux lettres (info par exemple) ;
  2. le nom de domaine Internet auquel appartient le serveur qui dessert cette boîte aux lettres (par exemple piapplications.eu).

Ces deux parties sont réunies pour former l'adresse courriel via le symbole @ (avec notre exemple info@piapplications.eu).

Pour autant, le client ne s'adresse pas directement au serveur qui gère la boîte aux lettres du destinataire. Pour le comprendre, imaginez un même message adressé à 1000 clients différents avec chacun une boîte aux lettre sur un domaine Internet différent. Cela obligerait le client à ouvrir 100 connexions simultanées donc 100 tâches simultanées ce qui poserait rapidement des difficultés techniques. A la place, chaque client converse avec son propre serveur SMTP auquel il remet l'ensemble de ses messages. Comme avec votre bureau de poste, c'est ce serveur SMTP qui est ensuite chargé d'expédier vos messages vers les différents serveurs gérant les boîtes aux lettres que vous cherchez à contacter.

Le fonctionnement d'un serveur SMTP est assez simple : il regarde si les boîtes aux lettres des destinataires sont gérées sur son domaine auquel cas il les remet directement aux serveurs qui supportent ces boîtes. Si ce n'est pas le cas, il remet les messages restant à un autre serveur SMTP (mécanisme de relais). Le processus se réitère jusqu'à ce que tous les messages aient été remis. Il peut arriver qu'une adresse n'existe pas ou que le serveur qui supporte sa boîte aux lettres soit déconnectés. Aussi, si au bout d'un certain temps (en général 4 heures), le serveur SMTP auquel est connecté le client ne reçoit pas d'acquittement de remise d'un ou plusieurs messages, il en informe son client par un message.

En raison de leur simplicité, les serveurs SMTP sont rapidement devenus la cible des spammeurs. Pour limiter le spam, la première évolution de SMTP a été de mettre en place un mécanisme d'authentification reposant sur un compte. Pour pouvoir envoyer un message, le client doit posséder un compte et son mot de passe auprès du serveur auquel il est abonné. Alors que les courriels se substituaient progressivement au courrier papier, la sensibilité du contenu des messages n'autorisait plus leur circulation en clair. En d'autres termes, il fallait les mettre sous enveloppe. C'est ce que réalise le chiffrement. Ce chiffrement peut répondre au protocole SSL ou TLS. SSL est l'ancienne version imaginée par Netscape et il faut désormais lui préféré TLS. Chaque protocole de chiffrement répond aussi à une version de spécification 1.0, 1.1 ou 1.2 par exemple pour TLS.

La première mise en place du chiffrement est connue sous le nom de STARTTLS. Ce protocole à l'avantage d'être compatible avec SMTP. Il s'agit en fait d'une évolutions des commandes SMTP. Lorsque le serveur reçoit la commande STARTTLS, il modifie son comportement pour entamer une négociation qui permettra le chiffrement du dialogue par la suite (SSL ou TLS). Si le serveur ne supporte pas le chiffrement, il l'indiquera au client qui pourra alors décider de poursuivre ou non l'envoi en utilisant le protocole SMTP basique. Notez qu'avec ce protocole, si un mécanisme d'authentification du client est nécessaire, il se produit après la négociation de chiffrement. L'intérêt de cette extension est de conserver une totale compatibilité avec SMTP ce qui autorise son utilisation sur le port 25 (port par défaut). Notez toutefois, que le port 587 est le port par défaut à utiliser lorsque c'est possible.

La seconde évolution nommée "submission" (ou SMTPS) n'est pas immédiatement compatible du protocole SMTP. Elle fonctionne de façon analogue à HTTPS en exigeant une négociation de chiffrement en préalable au dialogue via un certificat X.509. Une fois le canal établi, le dialogue SMTP s'y poursuit (intégrant une éventuelle authentification). Le port par défaut de ce mode est 465.

Javamail pour envoyer des messages.

Concernant javamail, nous considérons ici la version 1.5.2 de mai 2014. L'envoi d'un courriel s'effectue en 4 étapes :

  1. paramétrage des propriétés système de la JVM et éventuellement d'un authentificateur (objet implémentant l'interface Authenticator) ;
  2. ouverture d'une session SMTP (objet de classe Session) ;
  3. construction du message (comportant plusieurs parties (multipart) ou non) ;
  4. envoi du message.

Le premier point permet de fixer le mode de chiffrement éventuel ainsi que l'éventuel authentificateur. Si vous utilisez le mode submission, vous devez impérativement intégrer le certificat X.509 de confiance auquel se réfère le serveur SMTP au magasin cacerts du JRE ou du JDK utilisé.

Nous donnons ci-dessous un exemple complet (les données étant toutefois fictives) permettant d'envoyer un message en utilisant l'un des 3 modes : clair, STRATTLS ou submission. Pour les deux derniers, il est possible d'utiliser soit SSL, soit TLS.

package proto002;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

/**
 * ATTENTION : Le certificat du serveur Postfix doit être intégré en tant que
 * certificat de confiance dans le keystore cacerts du
 * JRE utilisé ($JRE/lib/security) sinon la connexion SSL/TLS ne se fera pas,
 * (erreur de type "unable to find valid certification path to requested target").
 * @author (c) PiApplications 2014.
 */
public class Proto002
{
  // Aménagez les attributs en fonction du test à réaliser : ici un envoi en clair sans authentification
  private String _sSubject = "Test de messagerie.";
  private String _sBody = "Vous arrivez à lire ce message : la messagerie fonctionne";
  private String _sUser;
  private String _sPassword;
  private String _sTo = "moncompte@mysmtp.net";
  private String _sFrom = "my@domain.net";
  private String _sSmtpHost = "smtp.mysmtp.net";
  private String _sSmtpProtocol = "clear";
  private int _iSmtpPort = 25;

  /**
   * Constructeur.
   */
  protected Proto002()
  {
  }
  
  private void loadConfiguration(String sFile)
      throws IOException
  {
    try (BufferedReader brd = new BufferedReader(new InputStreamReader(new FileInputStream(sFile), "UTF-8")))
    {
      String sLine;
      while((sLine = brd.readLine()) != null)
      {
        sLine = sLine.trim();
        if (sLine.isEmpty())
          continue;
        if (sLine.charAt(0) == '#')
          continue;
        int i = sLine.indexOf("=");
        if (i > 0 && i < sLine.length() - 1)
        {
          String sKey = sLine.substring(0, i).trim().toLowerCase();
          String sValue = sLine.substring(i + 1).trim();
          if (!sKey.isEmpty() && !sValue.isEmpty())
          {
            switch(sKey)
            {
              case "to":
                _sTo = sValue;
                break;
              case "from":
                _sFrom = sValue;
                break;
              case "subject":
                _sSubject = sValue;
                break;
              case "body":
                _sBody = sValue;
                break;
              case "smtpuser":
                _sUser = sValue;
                break;
              case "smtppassword":
                _sPassword = sValue;
                break;
              case "smtphost":
                _sSmtpHost = sValue;
                break;
              case "smtpport":
                _iSmtpPort = Integer.parseInt(sValue);
                break;
              case "smtpprotocol":
                switch(sValue.toLowerCase())
                {
                  case "clear":
                  case "starttls":
                  case "submission":
                    _sSmtpProtocol = sValue.toLowerCase();
                    break;
                }
                break;
            }
          }
        }
      }
    }
  }
  
  /**
   * Point d'entrée logique dans le programme (mode non statique).
   * @param args Arguments de la ligne de commande.
   * @throws javax.mail.MessagingException
   * @throws java.io.IOException
   */
  public void execute(String[] args)
      throws MessagingException,
             IOException
  {
    // Chargement de la configuration
    loadConfiguration(args[0]);
    // Récapitulatif de la configuration
    System.out.println(String.format("Serveur SMTP ..................: %s", _sSmtpHost));
    System.out.println(String.format("Port SMTP .....................: %d", _iSmtpPort));
    String s;
    if (_sUser == null)
      s = "aucun";
    else
      s = _sUser;
    System.out.println(String.format("Compte SMTP ...................: %s", s));
    if (_sPassword == null)
      s = "aucun";
    else
      s = _sPassword;
    System.out.println(String.format("Mot de passe ..................: %s", s));
    System.out.println(String.format("Protocole de transport ........: %s", _sSmtpProtocol));
    System.out.println(String.format("Emetteur ......................: %s", _sFrom));
    System.out.println(String.format("Destinataire ..................: %s", _sTo));
    System.out.println(String.format("Objet .........................: %s", _sSubject));
    System.out.println(String.format("Texte .........................: %s", _sBody));
    // Initialisation des propriétés système pour l'émission de courriels
    Authenticator ath = null;
    Properties prs = new Properties();
    prs.put("mail.from", _sFrom);
    // Mise en place des propriétés système pour l'envoi de courriels
    prs.put("mail.smtp.host", _sSmtpHost);
    switch(_sSmtpProtocol.toLowerCase())
    {
      case "starttls":
        prs.put("mail.smtp.starttls.enable","true");
        prs.put("mail.smtp.port", _iSmtpPort);
        break;
      case "startssl":
        prs.put("mail.smtp.starttls.enable","true");
        prs.put("mail.smtp.socketFactory.port", _iSmtpPort);
        prs.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        prs.put("mail.smtp.socketFactory.fallback", "false");
        break;
      case "submission":
        prs.put("mail.smtp.ssl.enable", "true");
        break;
      case "submissionssl":
        prs.put("mail.smtp.ssl.enable", "true");
        prs.put("mail.smtp.socketFactory.port", _iSmtpPort);
        prs.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        prs.put("mail.smtp.socketFactory.fallback", "false");
        break;
    }
    // Prise en compte de l'authentification
    boolean fAuth = false;
    if (_sUser != null && !_sUser.isEmpty() && 
        _sPassword != null && ! _sPassword.isEmpty())
    {
      fAuth = true;
      prs.put("mail.smtp.user", _sUser);
      prs.put("mail.smtp.auth", "true");
      ath = new SMTPAuthenticator(_sUser, _sPassword);
    }
    Session sss;
    if (fAuth)
      sss = Session.getInstance(prs, ath);
    else
      sss = Session.getInstance(prs);
    MimeMessage mms = new MimeMessage(sss);
    mms.setText(_sBody);
    mms.setSubject(_sSubject);
    mms.setFrom(new InternetAddress(_sFrom));
    mms.addRecipient(Message.RecipientType.TO, new InternetAddress(_sTo));
    Transport.send(mms);
    System.out.println(String.format("Courriel émis vers %s", _sTo));
  }
  
  /**
   * Point d'entrée physique du programme (mode statique).
   * @param args Arguments de la ligne de commande.
   */
  public static void main(String[] args)
  {
    try
    {
      Proto002 prg = new Proto002();
      prg.execute(args);
    }
    catch(Throwable thw)
    {
      System.err.println(MessageFormat.format("ERREUR : ({0}) {1}\n", 
          thw.getClass().getName(), thw.getLocalizedMessage()));
      thw.printStackTrace(System.err);
    }
  }
}

Merci à javamail !

(c) PiApplications 2015