1. --****************************************************************************
  2. -- Software: SMTP_CLASS
  3. -- Version:  0.5
  4. -- Date:     2011-11-04
  5. -- Author:   Valentin Schmidt
  6. -- Contact:  fluxus@freenet.de
  7. -- License:  Freeware
  8. --
  9. -- Requirements/Dependencies:
  10. -- + Multiuser Xtra
  11. -- + Base64 encoder class (internally either using a fast xtra like e.g. Crypto Xtra or slow lingo code)
  12. --
  13. --****************************************************************************
  14.  
  15. -- server
  16. property pServer           -- Address of SMTP server
  17. property pPort             -- SMTP port number
  18. property pUsername         -- Username for SMTP AUTH
  19. property pPassword         -- Password for SMTP AUTH
  20.  
  21. -- email
  22. property pProps
  23. property pRecipients       --
  24. property pMailContent      --
  25.  
  26. -- private stuff
  27. property pScriptVersion
  28. property pMUXtra           -- Instance of Multiuser Xtra
  29. property pStatus           -- Status of messaging
  30. property pEOL
  31. property pBase64Encoder
  32. property pTextEncoding
  33. property pMimeTypes
  34.  
  35. ----------------------------------------
  36. -- CONSTRUCTOR
  37. ----------------------------------------
  38. on new (me, server, port, user, password)
  39.   pScriptVersion = "0.5"
  40.   pTextEncoding = "UTF-8"
  41.   pServer = server
  42.   pPort = port
  43.   pUsername = user
  44.   pPassword = password
  45.   pBase64Encoder = script("BASE64_CLASS").new()
  46.   pMimeTypes =[:]
  47.   pMimeTypes["jpg"] = "image/jpeg"
  48.   pMimeTypes["png"] = "image/png"
  49.   pMimeTypes["gif"] = "image/gif"
  50.  
  51.   pEOL = numToChar(13)&numToChar(10)
  52.   the randomseed = the ticks
  53.   return me
  54. end
  55.  
  56. ----------------------------------------
  57. -- start sending mail
  58. ----------------------------------------
  59. on sendMail (me, props)
  60.  
  61.   -- REQUIRED:
  62.   --  to
  63.   --  from
  64.   --  subject
  65.   --  message
  66.  
  67.   -- OPTIONAL:
  68.   --  cc
  69.   --  bcc
  70.   --  headers
  71.   --  attachments
  72.   --  callback
  73.   --  callback_target  
  74.  
  75.   -- allow strings
  76.   if stringP(props["to"]) then props["to"] = [props["to"]]
  77.   if stringP(props["cc"]) then props["cc"] = [props["cc"]]
  78.   if stringP(props["bcc"]) then props["bcc"] = [props["bcc"]]
  79.   if stringP(props["headers"]) then props["headers"] = [props["headers"]]
  80.   if stringP(props["attachments"]) then props["attachments"] = [props["attachments"]]
  81.  
  82.   -- defaults
  83.   if voidP(props["cc"]) then props["cc"] = []
  84.   if voidP(props["bcc"]) then props["bcc"] = []
  85.   if voidP(props["headers"]) then props["headers"] = []
  86.   if voidP(props["attachments"]) then props["headers"] = []
  87.   if voidP(props["callback_target"]) then props["callback_target"] = _movie
  88.  
  89.   pProps = props
  90.  
  91.   pRecipients = props["to"].duplicate()
  92.   repeat with a in props["cc"]
  93.     pRecipients.add(a)
  94.   end repeat
  95.   repeat with a in props["bcc"]
  96.     pRecipients.add(a)
  97.   end repeat
  98.  
  99.   -- initialize connection instance
  100.   pMUXtra = xtra("multiuser").new()
  101.  
  102.   -- define callback handlers and error check
  103.   errCode = pMUXtra.setNetMessageHandler(#messageHandler, me)
  104.   if errCode <> 0 then
  105.     -- error defining callback handlers
  106.     me.dbg("Error with setNetMessageHandler")
  107.   else
  108.    
  109.     pMailContent = ""
  110.    
  111.     -- DEFAULT HEADERS
  112.     put "From: "& pProps["from"] &pEOL after pMailContent
  113.     put "Subject: "& pProps["subject"] &pEOL after pMailContent
  114.     put "To: "& implode(", ", pProps["to"]) &pEOL after pMailContent
  115.    
  116.     if count(pProps["cc"]) then
  117.       put "Cc: "& implode(", ", pProps["cc"]) &pEOL after pMailContent
  118.     end if
  119.    
  120.     if count(pProps["bcc"]) then
  121.       put "Bcc: "& implode(", ", pProps["bcc"]) &pEOL after pMailContent
  122.     end if
  123.    
  124.     put "Reply-To: "& pProps["from"] &pEOL after pMailContent
  125.     put "X-Mailer: SMTP CLASS v"&pScriptVersion &pEOL after pMailContent
  126.    
  127.     -- ADDITIONAL HEADERS
  128.     repeat with h in pProps["headers"]
  129.       put h &pEOL after pMailContent
  130.     end repeat
  131.    
  132.     if count(pProps["attachments"])=0 then
  133.      
  134.       put "Content-Type: text/plain; charset="&pTextEncoding &pEOL after pMailContent
  135.       put pEOL& pProps["message"] &pEOL&pEOL&"." after pMailContent
  136.      
  137.     else
  138.       --MIME
  139.       bound=string(random(the maxinteger)) -- "----=_NextPart_000_001F_01C3C4D5.E21E2390"      
  140.      
  141.       put "MIME-Version: 1.0" &pEOL after pMailContent
  142.       put "Content-Type: multipart/mixed; boundary="&QUOTE&bound&QUOTE &pEOL &pEOL after pMailContent
  143.       put "This is a multi-part message in MIME format."&pEOL &pEOL after pMailContent
  144.      
  145.       put "--"&bound &pEOL after pMailContent
  146.       put "Content-Type: text/plain; charset="&pTextEncoding &pEOL after pMailContent
  147.       put "Content-Transfer-Encoding: 7bit" &pEOL &pEOL after pMailContent
  148.       --put "Content-Transfer-Encoding: quoted-printable" &pEOL &pEOL after pMailContent
  149.      
  150.       put pProps["message"] &pEOL after pMailContent
  151.      
  152.       cnt = count(pProps["attachments"])
  153.       repeat with i = 1 to cnt
  154.         fn = pProps["attachments"].getPropAt(i)
  155.         t = fn.char[fn.length-2..fn.length]
  156.         mt = pMimeTypes[t]
  157.         if voidP(mt) then mt = "application/octet-stream"
  158.        
  159.         ba = pProps["attachments"][i]
  160.         put "--"&bound &pEOL after pMailContent
  161.         put "Content-Type: "&mt&"; name="&QUOTE&fn&QUOTE &pEOL after pMailContent
  162.         put "Content-Transfer-Encoding: base64"&pEOL after pMailContent
  163.         put "Content-Disposition: attachment; filename="&QUOTE&fn&QUOTE &pEOL &pEOL after pMailContent
  164.         me.dbg("ENCODING" && fn)
  165.         put pBase64Encoder.encode(ba, true) &pEOL &pEOL after pMailContent
  166.       end repeat
  167.      
  168.       put "--"&bound&"--" &pEOL &pEOL&"." after pMailContent
  169.      
  170.     end if
  171.    
  172.     maxMessageSize = max(16*1024, pMailContent.length + 64)
  173.     errCode = pMUXtra.setNetBufferLimits(16 * 1024, maxMessageSize, 100)
  174.    
  175.     -- make connection and error check
  176.     errCode = pMUXtra.ConnectToNetServer("String", "String", pServer, pPort, "smtpClient", 1)
  177.     if errCode = 0 then
  178.       message = "ConnectToNetServer sent to server"
  179.       pStatus = "ConnectToNetServer sent"
  180.     else
  181.       message = "Error sending ConnectToNetServer to server"
  182.       pStatus = "ERROR"
  183.     end if
  184.     me.dbg(message)
  185.    
  186.   end if
  187. end
  188.  
  189. ----------------------------------------
  190. -- handle MU messages
  191. ----------------------------------------
  192. on messageHandler (me)
  193.   -- get message from message queue
  194.   newMsg = pMUXtra.getNetMessage()
  195.   -- error check
  196.   errCode = newMsg.errorCode
  197.   if (errCode = 0) then
  198.     -- set local variables
  199.     senderID = newMsg.senderID
  200.     subject = newMsg.subject
  201.     content = newMsg.content
  202.    
  203.     case(TRUE) of:
  204.         -- initial response from server
  205.       (senderID = "System" and subject = "ConnectToNetServer") :
  206.         message = "ConnectToNetServer successful"
  207.         pStatus = "ConnectToNetServer"
  208.        
  209.       (content starts "334") :
  210.         -- if the SMTP returns "334" then it's ready for an SMTP Authentification
  211.         case(pStatus) of
  212.           "AUTH_LOGIN":
  213.             errCode = me.snd(pBase64Encoder.encode(pUsername))
  214.             if errCode = 0 then
  215.               message = "USERNAME sent to server"
  216.               pStatus = "USER"
  217.             else
  218.               message = "Error with USERNAME"
  219.               pStatus = "ERROR"
  220.             end if
  221.            
  222.           "USER":
  223.             errCode = me.snd(pBase64Encoder.encode(pPassword))
  224.             if errCode = 0 then
  225.               message = "PASSWORD sent to server"
  226.               pStatus = "PASSWORD"
  227.             else
  228.               message = "Error with PASSWORD"
  229.               pStatus = "ERROR"
  230.             end if
  231.         end case
  232.        
  233.       (content starts "235") :
  234.         -- if the SMTP returns "235" then send MAIL FROM
  235.         errCode = me.snd("MAIL FROM:" && pProps["from"])
  236.         if errCode = 0 then
  237.           message = "MAIL FROM message sent to server"
  238.           pStatus = "MAIL_FROM"
  239.         else
  240.           message = "Error with MAIL FROM message"
  241.           pStatus = "ERROR"
  242.         end if
  243.        
  244.       (content starts "220") :
  245.         -- if the SMTP returns "220" then it's ready for an SMTP session
  246.         -- send HELO command with the sender's domain name
  247.         the itemDelimiter = "@"
  248.         domain = pProps["from"].item[2]
  249.         errCode = me.snd("HELO" && domain)
  250.         if errCode = 0 then
  251.           message = "HELO message sent to server"
  252.           pStatus = "HELO"
  253.         else
  254.           message = "Error with HELO message"
  255.           pStatus = "ERROR"
  256.         end if
  257.        
  258.       (content starts "250") :
  259.         -- an STMP server response of "250"
  260.        
  261.         case(pStatus) of
  262.             -- the most recent step in the sequence is stored in pStatus
  263.             -- use pStatus to determine the next sequence
  264.            
  265.           "HELO" :
  266.             if voidP(pUsername) then
  267.              
  268.               -- if the most recent step in the sequence was HELO then send MAIL FROM
  269.               errCode = me.snd("MAIL FROM:"&&pProps["from"])
  270.               if errCode = 0 then
  271.                 message = "MAIL FROM message sent to server"
  272.                 pStatus = "MAIL_FROM"
  273.               else
  274.                 message = "Error with MAIL FROM message"
  275.                 pStatus = "ERROR"
  276.               end if
  277.              
  278.             else
  279.              
  280.               -- if the most recent step in the sequence was HELO then send AUTH LOGIN
  281.               errCode = me.snd("AUTH LOGIN")
  282.               if errCode = 0 then
  283.                 message = "AUTH LOGIN message sent to server"
  284.                 pStatus = "AUTH_LOGIN"
  285.               else
  286.                 message = "Error with MAIL FROM message"
  287.                 pStatus = "ERROR"
  288.               end if
  289.             end if
  290.            
  291.           "MAIL_FROM", "RCPT_TO" :
  292.             if (count(pRecipients)>0) then
  293.              
  294.               tTo = pRecipients[1]
  295.               pRecipients.deleteAt(1)
  296.              
  297.               errCode = me.snd("RCPT TO:"&&tTo)
  298.               if errCode = 0 then
  299.                 message = "RCPT TO message sent to server"
  300.                 pStatus = "RCPT_TO"
  301.               else
  302.                 message = "Error with RCPT TO message"
  303.                 pStatus = "ERROR"
  304.               end if
  305.              
  306.             else
  307.              
  308.               -- if the most recent step in the sequence was RCPT TO then send DATA
  309.               errCode = me.snd("DATA")
  310.               if errCode = 0 then
  311.                 message = "DATA message sent to server"
  312.                 pStatus = "DATA"
  313.               else
  314.                 message = "Error with DATA message"
  315.                 pStatus = "ERROR"
  316.               end if
  317.              
  318.             end if
  319.            
  320.           "Message Content" :
  321.             -- if the most recent step in the sequence was message content then send QUIT
  322.             errCode = me.snd("QUIT")
  323.             if errCode = 0 then
  324.               message = "QUIT message sent to server"
  325.               pStatus = "QUIT"
  326.             else
  327.               message = "Error with QUIT message"
  328.               pStatus = "ERROR"
  329.             end if
  330.            
  331.         end case
  332.        
  333.       (content starts "354") :
  334.         -- an SMTP server respons of "354" means it's ready to receive message content
  335.         errCode = me.snd(pMailContent)
  336.         if errCode = 0 then
  337.           message = "Message content sent to server"
  338.           pStatus = "Message Content"
  339.         else
  340.           message = "Error with email content"
  341.           pStatus = "ERROR"
  342.         end if
  343.        
  344.       (content starts "221") :
  345.         -- an SMTP server response of "221"
  346.         if errCode = 0 then
  347.           message = "Email sent successfully"
  348.           pStatus = "Success"
  349.          
  350.           -- clear multiuser xtra reference from memory
  351.           me.clearMU()
  352.          
  353.           if NOT voidP(pProps["callback"]) then
  354.             call(pProps["callback"], [pProps["callback_target"]], 1)
  355.           end if
  356.          
  357.         else
  358.           message = "Error in server's response to QUIT"
  359.           pStatus = "ERROR"
  360.         end if
  361.        
  362.       otherwise:
  363.         -- an unexpected SMTP server response
  364.         message = "Server Message: " & content.line[1]
  365.         pStatus = "ERROR"
  366.        
  367.     end case
  368.    
  369.   else -- if errCode <> 0
  370.     if senderID = "System" and subject = "ConnectionProblem" then
  371.       -- trap messages with the subject "ConnectionProblem".
  372.       -- useful to eliminate the alert box that would otherwise appear
  373.       nothing
  374.     else
  375.       -- put other errors into status field
  376.       message = pMUXtra.GetNetErrorString(errCode)
  377.       pStatus = "ERROR"
  378.     end if
  379.   end if
  380.  
  381.   if pStatus = "ERROR" then
  382.     -- clear multiuser xtra reference from memory
  383.     me.clearMU()
  384.     if NOT voidP(pProps["callback"]) then
  385.       call(pProps["callback"], [pProps["callback_target"]], 0)
  386.     end if
  387.   end if
  388.  
  389.   me.dbg(message)
  390. end
  391.  
  392. ----------------------------------------
  393. --
  394. ----------------------------------------
  395. on snd (me, str)
  396.   return pMUXtra.SendNetMessage("", "", str&pEOL)
  397. end
  398.  
  399. ----------------------------------------
  400. -- clear out netMessageHandler callbacks and void instance
  401. ----------------------------------------
  402. on clearMU (me)
  403.   -- clear all callbacks by setting #handlerSymbol parameter to 0
  404.   errCode = pMUXtra.setNetMessageHandler(0, me)
  405.   errCode = pMUXtra.setNetMessageHandler(0, me, "ConnectToNetServer")
  406.  
  407.   -- clear instance of the Multiuser xtra
  408.   pMUXtra = VOID
  409.   me.dbg("CLOSE MUXtra")
  410. end
  411.  
  412. ----------------------------------------
  413. -- shows network status information
  414. ----------------------------------------
  415. on dbg (me, s)
  416.   put "SMTP:" && s -- delete/comment line for silent execution
  417. end
  418.  
  419. ----------------------------------------
  420. --
  421. ----------------------------------------
  422. on implode (delim, l)
  423.   str = ""
  424.   repeat with i=1 to l.count
  425.     put l[i]&delim after str
  426.   end repeat
  427.   return str.char[1..(str.length-delim.length)]
  428. end
  429.  
[raw code]