send_mail.anubis 9.87 KB
/*
 * Created by PyramIDE.
 * User: ricard
 * Date: 22/12/2008
 * Time: 17:49
 * 
 */


read tools/basis.anubis   
read tools/connections.anubis
read tools/printable_tree.anubis
read network/dns.anubis
read network/tools.anubis
read system/files.anubis
read system/string.anubis
read system/data_io.anubis
read system/logger.anubis

read calexium_lib/net_services_protocols/logger_service.anubis
read calexium_lib/mail/send_mail_type.anubis

read lexers/enhanced_status.anubis
read smtp_server_extensions.anubis
read authentication.anubis
read smtp_client.anubis

public define String send_mail_log    = "SendMail".
public define LogMask send_mail_mask  = logMask("send_mail").

define Int        _sendmail_time_out     = 300.  //5 minutes of timeout

  public type SendMailResult:
    ok,
    error,
    reply(Int code, String enhanced_status, List(String) lines).
  
public type Smtp_Recipient:
  smtp_recipient(String         email,      //email address of the recipient

                 Send_Tries     nb_tries,
                 Int            uid,        //parameter used by handle_status (mail_uid)
                 Int            uid2        //same                            (contact_id)
                 ). 


public define Smtp_Recipient
  smtp_recipient
  (
    String recipient
  )=
  smtp_recipient(recipient, send_tries(0), 0,0)
.

public type Send_Param:
  send_param( Str_HostName  host_name,        //domain of the sender like "mail.calexium.com"
              Str_Sender    sender,           //sender email address
              List(Data_IO) mail_parts,       //all parts of the email to send
              List(Smtp_Recipient) recipients,  //recipient email address
              SmtpAuth      auth,             //
              Str_UID       mail_uid,
              Int           last_smtp_code  //0 means it's the first time
              ).
            



define Result(SmtpClientResult, One)
  sm_MAIL_FROM
  (                                            
    RWStream                  conn,       //TCP connection
    Str_Sender                sender,     //email address of the sender
    List(Data_IO)             mail_parts, //all parts to send
    Smtp_Recipient            recipient,  //contain email address and uid
    SmtpClientSession         sm_session, //smtp session
    (Int) -> One              progress_report,  //don't know what it is for the moment
    (LogLevel, String) -> One logger      //logger
  ) =
  with enhanced_status = sm_session.enhanced_status,
  since recipient is smtp_recipient(rcpt_address, try, uid, uid2),
  //send MAIL FROM
  if send_mail_from(conn, sender.str, sm_session, _sendmail_time_out, logger) is
  {
    error(result)   then error(result), 
    ok(_)           then
      //send RCPT TO
      if send_recipient(conn, rcpt_address, sm_session, _sendmail_time_out, logger) is
      {
        error(result)   then error(result), 
        ok(_)           then
          //send DATA
          if send_data(conn, sm_session, _sendmail_time_out, logger) is
          {
            error(result)   then error(result), 
            ok(_)           then
              if rewind(mail_parts) then
                if send_content(conn, mail_parts, sm_session, _sendmail_time_out, progress_report, logger) is
                {
                  error(result)   then error(result), 
                  ok(_)           then
                    //send_quit(conn, sm_session, _sendmail_time_out, logger);
                    logger(logTrace, "SENT Mail FROM "+sender.str+" TO "+rcpt_address);
                    ok(unique)
                }
              else
                error(error)
          }
      }
  }.

define Result(SmtpClientResult, One)
  send_to_recipients_list
  (
    RWStream                  conn,       //TCP connection
    Send_Param                param,
    List(Smtp_Recipient)      recipients,  //recipients with email to send
    SmtpClientSession         sm_session, //smtp session
    (Int) -> One              progress_report,  //don't know what it is for the moment
    (LogLevel, String) -> One logger,      //logger
    (Result(SmtpClientResult, One), Smtp_Recipient) -> One handle_result,
    Bool                      unique_recipient
  )=
  if recipients is
  {
    []              then ok(unique),
    [ rcpt . tail ] then
        with result = sm_MAIL_FROM(conn, param.sender, param.mail_parts, rcpt, sm_session, progress_report, logger),
        //here we apply the function provided by application to handle the result 
        handle_result(result, rcpt);
        //if there is another recipient we send a RSET and we are able to send the 
        //email to the next recipient within the same smtp session
        if length(tail) > 0 then
          send_rset(conn, sm_session, _sendmail_time_out, logger);
          send_to_recipients_list(conn, param, tail, sm_session, progress_report, logger, handle_result, unique_recipient)
        else
          if unique_recipient then
            result
          else
            ok(unique)
  }
  .
  
   Apply the whole protocol:
   
   // we can have a list of part file in param type. This can be very useful when we want
   // to send file as mailing. For the mailing, in each mail, only the header is different of the 
   // other mails. The body part is the same. Then, the mailing sender can generate the header in one 
   // file and keep the body in other file wich can be given to send_mail function as last file in the list
public define Result(SmtpClientResult, One)
  send_mail
  (
    RWStream        conn,     
    Send_Param      param,
    (SmtpClientSession, Send_Param) -> Result(SmtpClientResult, Send_Param)   prepare_mail_callback,
    (Int) -> One    progress_report,
    (LogLevel, String) -> One logger,
    (Result(SmtpClientResult, One), Smtp_Recipient) -> One handle_result    
  ) =
   //
   // Our connection to the SMTP server is opened. We just have to apply the protocol.
   //
  if receive_reply(conn, false, _sendmail_time_out, logger) is
  {
    error               then  error(error),
    timeout             then  error(timeout),
    no_auth_method      then  error(no_auth_method),
    bad_reply           then  error(bad_reply),
    reply(code, status, lines)  then 
      if code = 220 then
        //send EHLO
        if send_ehlo(conn, param.host_name.str, _sendmail_time_out, logger) is
        {
          error(result)       then error(result),
          ok(sm_session) then
              if do_auth(conn, param.auth, sm_session, _sendmail_time_out, logger) is
              {
                error(auth_result) then error(auth_result),
                ok(_) then
                  if prepare_mail_callback(sm_session, param) is
                  {
                    error(prepare_result) then error(prepare_result),
                    ok(new_param)         then 
                      with result =  
                        if length(new_param.recipients) > 1 then
                          send_to_recipients_list(conn, new_param, new_param.recipients, sm_session, progress_report, logger, handle_result, false)
                        else
                          send_to_recipients_list(conn, new_param, new_param.recipients, sm_session, progress_report, logger, handle_result, true),
                        send_quit(conn, sm_session, _sendmail_time_out, logger);
                        result                      
                  }
              }
        }
      else if code = 550 then
        //some server answer "550 5.7.1 Client host rejected: cannot find your reverse hostname, [88.181.64.17]"
        //before anything, then we try to extract the enhanced status if exists for relaying the mail with the ISP
        logger(logDebug, "Converting a 550 error for connection to a 432 error as distant server may be temporary overloaded.");
        error(reply(432, status, lines))
      else 
        error(reply(code, status, lines))
  }.

public define Result(SmtpClientResult, One)
  send_mail
  (
    RWStream        conn,     
    Send_Param      param,
    (SmtpClientSession, Send_Param) -> Result(SmtpClientResult, Send_Param)   prepare_mail_callback,
    (Result(SmtpClientResult, One), Smtp_Recipient) -> One handle_result
  ) =
  send_mail(conn, 
            param, 
            prepare_mail_callback,
            (Int _) |-> unique,
            (LogLevel level, String txt) |-> if level is logTrace then logTrace(send_mail_log, send_mail_mask, txt)
                                                                  else log(level, send_mail_log, txt),
            handle_result                                                                  
            ).

public define Result(SmtpClientResult, One)
  send_mail
  (
    RWStream        conn,     
    Send_Param      param,
    (Result(SmtpClientResult, One), Smtp_Recipient) -> One handle_result
  ) =
  send_mail(conn, 
            param, 
            (SmtpClientSession sm_session, Send_Param parameter) |-> ok(parameter),
            handle_result
            ).

public define Result(SmtpClientResult, One)
  send_mail
  (
    String          server,
    Word32          port,
    Send_Param      param,
  )=
  if resolve_address(server) is
  {
    failure           then
      println("IP address NOT found for Mail server ["+server+"]");
      logWarning(send_mail_log,"IP address NOT found for Mail server ["+server+"]");
      error(error),

    success(ip_addr)  then
      with ip_txt = ip_addr_to_string(ip_addr),
      logTrace(send_mail_log, send_mail_mask, "send_mail: Trying to connect to "+ip_txt);
      println("send_mail: Trying to connect to "+ip_txt);
      if (Result(NetworkConnectError,RWStream))connect(ip_addr, port) is 
      {
        error(e)               then 
          logTrace(send_mail_log, send_mail_mask, "send_mail: Can't connect to "+ip_txt); 
          error(error),
      
        ok(server_conn)        then 
          send_mail(server_conn, param, (Result(SmtpClientResult, One) dum, Smtp_Recipient my) |-> println("send_mail Result(SmtpClientResult, One) ignored ")),
      }
  }.