SMTPConnection.java

package fr.umlv.ji.tcp.smtp;
import java.io.*;
import java.net.*;
/**
 * Classe premettant d'envoyer un courrier électronique en utilisant
 * le proctocole SMTP (RFC 821).
 */
public class SMTPConnection {
  /** Port TCP par défaut des serveurs SMTP. */
  public final static int SMTP_PORT = 25;
  /** Socket de la connexion TCP. */
  private Socket s;
  /** Flot en écriture sur la connexion TCP. */
  private PrintWriter output;
  /** Flot en lecture sur la connexion TCP. */
  private LineNumberReader input;
  /** État de la connexion vers le serveur SMTP. */
  private boolean connected = false;
  /** Adresse électronique de l'émetteur. */
  private String fromAddr;
  /** Adresse Internet du serveur SMTP. */
  InetAddress mailHost;
  /** Marqueur de fin de ligne pour SMTP. */
  static final String EOL = "\r\n";  
  /** Contructeur récupérant le nom du serveur SMTP et l'adresse 
      de courrier électronique par défaut de l'émetteur. */
  public SMTPConnection() throws IOException {
    // Récupération du nom du serveur de mail dans la propriété
    // système "mail.host"
    String mailHostName = System.getProperty("mail.host");
    // Récupération de l'adresse Internet correspondant au nom du serveur
    // En son absence, on utilise la machine locale comme serveur
    if (mailHostName==null)
      mailHost = InetAddress.getLocalHost();
    else
      mailHost = InetAddress.getByName(mailHostName);
    // Récupération de l'adresse éléctronique de l'émetteur dans la
    // propriété système "user.fromaddr"
    fromAddr = System.getProperty("user.fromaddr");
    // En son absence, on utilise <user.name>@<machine.locale>
    if (fromAddr==null) {
      fromAddr = System.getProperty("user.name");
      fromAddr = fromAddr + "@" + mailHostName;
    }
  }
  /** Création de la connexion TCP vers le serveur SMTP
      et présentation du client. */
  public void connect() throws IOException {
    // Si la connexion est déjà établie, il n'y a rien à faire
    if (connected) return;
    // Appel au constructeur de la socket, qui établit la connexion
    s = new Socket(mailHost,SMTP_PORT);
    // Récupération du flot en lecture sur la connexion
    input = new LineNumberReader(
         new InputStreamReader(s.getInputStream(), "ASCII")); 
    // Lecture de la réponse
    readResponse();
    // Récupération du flot en écriture sur la connexion
    output = new PrintWriter(
          new OutputStreamWriter(s.getOutputStream(), "8859_1"));
    // Envoi de la requête de présentation
    sendLine("EHLO " + InetAddress.getLocalHost().getHostName());
    // Lecture des lignes de réponses en vérifiant que le serveur
    // SMTP accepte les caractères sur 8 bits
    boolean support8bit = false;
    String line;
    do {
      line = input.readLine();
      System.err.println(line);
      if (line.indexOf("8BITMIME")!=-1)
    support8bit = true;
    } while (line.charAt(3)=='-');
    // Code de statut suivi d'un tiret: d'autres lignes suivent
    if (!support8bit)
      throw  new SMTPException("8bit message not supported");
    connected=true;     // Mise à jour de l'état de la connexion
  }
  /** Lecture des lignes constituant une réponse du serveur.
      Affichage de tout ce qui est lu. */
  void readResponse() throws IOException {
    String line;
    // On lit une ligne
    line = input.readLine();
    System.err.println(line);
    // En cas d'erreur, on lève une exception 
    if (line.charAt(0)=='4' || line.charAt(0)=='5')
      throw new SMTPException(line);
    // S'il y a encore des lignes à suivre, on rappelle la méthode
    if (line.charAt(3)=='-')
      readResponse();
  }
  /** Envoi d'un message contenant un texte utilisant le codage ISO8859-1.
      @param to      destinataire.
      @param subject sujet du message.
      @param message contenu du message. */
  public void sendMessage(String to, String subject, String message)
    throws IOException {
    // Si la connexion n'est pas établie, il faut l'établir
    if (!connected)
      connect();
    // Envoi des différentes requêtes et lecture de chaque
    // réponse du serveur 
    sendLine("MAIL FROM: " + fromAddr);
    readResponse();
    sendLine("RCPT TO: " + to);
    readResponse();
    sendLine("DATA");
    readResponse();
    // Ajout de trois lignes d'en-têtes avant l'envoi des données
    sendLine("Mime-Version: 1.0");
    sendLine("Content-type: text/plain; charset=\"iso-8859-1\"");
    sendLine("Subject: " + subject);
    // Une ligne vide termine les lignes d'en-têtes
    sendLine();
    // On envoie ensuite le corps du message
    sendLine(message);
    // Et enfin, une ligne ne contenant qu'un point 
    sendLine(".");
    // On lit ensuite la réponse
    readResponse();
  }
  /** Envoi d'une ligne au serveur. La ligne envoyée au serveur
      est également affichée.
      @param line la ligne à envoyer. */
  void sendLine(String line) throws IOException {
    output.print(line);
    output.print(EOL);
    output.flush();
    System.err.println(line);
  }
  /** Envoi d'une ligne vide. */
  void sendLine() throws IOException {
    sendLine("");
  }
  /** Déconnexion du serveur SMTP. */
  public void disconnect() throws IOException {
    if (connected) {
      // La déconnexion se fait par l'envoi d'une requête QUIT
      sendLine("QUIT");
      readResponse();
      // Ensuite, on ferme la socket qui ferme la connexion
      s.close();
      // Mise à jour de l'état de la connexion
      connected = false;
    }
  }
  /** Réinitialise le dialogue avec le serveur. Utilisé en cas
      d'erreur au cours du dialogue. */
  public void reset() throws IOException {
    // Il faut, avant tout, être connecté
    if (!connected) {
      connect();
    }
    // puis envoyer une requête RSET
    sendLine("RSET");
    readResponse();
  }
  /** Méthode appelée lors de la destruction de l'objet. Ferme proprement 
      la connexion avec le serveur. */
  protected void finalize() {
    try {
      disconnect();
    } catch(IOException ex) {}
  }
  /** Méthode envoyant le même message à une liste de destinataires,
      passés en argument sur la ligne de commande après le sujet du
      message. Le message est lu sur l'entrée standard. */
  public static void main(String[] args) throws IOException {
    // Vérification de la présence d'arguments sur la ligne de commande
    if (args.length<2) {
      System.err.println("Usage: "
            +"java fr.umlv.ji.tcp.smtp.SMTPConnection <subject> <dest>+");
      System.exit(1);
    }
    // Récupération du message sur l'entrée standard.
    System.out.println("Entrez votre message :");
    StringBuffer messageBuffer = new StringBuffer();
    LineNumberReader reader =
      new LineNumberReader(new InputStreamReader(System.in));
    String line;
    while ((line=reader.readLine()) != null) 
      messageBuffer.append(line);
    String message = messageBuffer.toString();
    // Établissement de la connexion vers le serveur
    SMTPConnection connexion = new SMTPConnection();
    connexion.connect();
    // Envoyer le message à chaque destinataire 
    for (int i=1; i<args.length; i++) {
      try {
    connexion.sendMessage(args[i],args[0],message);
      } catch(SMTPException e) {
    System.err.println(e);
    connexion.reset();
      }
    }
    connexion.disconnect(); // Fermer la connexion vers le serveur.
  }
}