447 lines
14 KiB
Plaintext
447 lines
14 KiB
Plaintext
IMPORT os
|
|
IMPORT util
|
|
|
|
DEFINE rv INTEGER
|
|
|
|
DEFINE logtext STRING
|
|
DEFINE smtplog base.StringBuffer
|
|
DEFINE ext2mime DYNAMIC ARRAY OF RECORD
|
|
ext STRING,
|
|
mime STRING,
|
|
encoding STRING
|
|
END RECORD
|
|
TYPE cnf_t RECORD
|
|
hostname STRING,
|
|
smtphost STRING,
|
|
smtpport INTEGER,
|
|
senderemail STRING,
|
|
clave STRING,
|
|
cnxtimeout INTEGER
|
|
END RECORD
|
|
DEFINE cnf cnf_t
|
|
|
|
&define MB(array, idx, line) \
|
|
LET array[idx] = line \
|
|
LET idx = idx + 1
|
|
|
|
FUNCTION genmail(senderemail, recipients, subject, mailtext, attachments, mailbody, rcpt)
|
|
DEFINE senderemail STRING
|
|
DEFINE subject STRING
|
|
DEFINE recipients DYNAMIC ARRAY OF RECORD mode STRING, recipient STRING END RECORD
|
|
DEFINE mailtext STRING
|
|
DEFINE sbmt base.StringBuffer
|
|
DEFINE i, found INTEGER
|
|
DEFINE rcpt DYNAMIC ARRAY OF STRING
|
|
DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD
|
|
DEFINE nl INTEGER
|
|
DEFINE nrcpt INTEGER
|
|
DEFINE nr INTEGER
|
|
DEFINE extension STRING
|
|
DEFINE mimeid INTEGER
|
|
DEFINE tz STRING
|
|
DEFINE boundary STRING
|
|
|
|
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
|
|
|
LET nl = 1
|
|
LET nr = 1
|
|
LET nrcpt = 0
|
|
|
|
# Add mail date
|
|
MB(mailbody, nl, SFMT("Date: %1 %2 %3\r", TODAY USING "ddd, dd mmm yyyy", TIME(CURRENT), tz))
|
|
|
|
# First add headers
|
|
MB(mailbody, nl, SFMT("From: %1\r", senderemail))
|
|
|
|
# Add user agent
|
|
MB(mailbody, nl, "User-Agent: maildemo sample program.\r")
|
|
|
|
# Add MIME version
|
|
MB(mailbody, nl, "MIME-Version: 1.0\r")
|
|
|
|
# Add To: recipients
|
|
LET found = 0
|
|
FOR i = 1 TO recipients.getLength()
|
|
IF recipients[i].mode == "To" THEN
|
|
IF found THEN
|
|
LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient))
|
|
ELSE
|
|
LET mailbody[nl]=SFMT("To: %1", recipients[i].recipient)
|
|
LET found = 1
|
|
END IF
|
|
MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient)))
|
|
END IF
|
|
END FOR
|
|
IF found == 1 THEN
|
|
LET mailbody[nl] = mailbody[nl].append("\r")
|
|
LET nl = nl + 1
|
|
END IF
|
|
|
|
# Add CC: recipients
|
|
LET found = 0
|
|
FOR i = 1 TO recipients.getLength()
|
|
IF recipients[i].mode == "CC" THEN
|
|
IF found THEN
|
|
LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient))
|
|
ELSE
|
|
LET mailbody[nl]=SFMT("CC: %1", recipients[i].recipient)
|
|
LET found = 1
|
|
END IF
|
|
MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient)))
|
|
END IF
|
|
END FOR
|
|
IF found == 1 THEN
|
|
LET mailbody[nl] = mailbody[nl].append("\r")
|
|
LET nl = nl + 1
|
|
END IF
|
|
|
|
# Add CC: recipients
|
|
LET found = 0
|
|
FOR i = 1 TO recipients.getLength()
|
|
IF recipients[i].mode == "BCC" THEN
|
|
IF found THEN
|
|
LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient))
|
|
ELSE
|
|
LET mailbody[nl]=SFMT("BCC: %1", recipients[i].recipient)
|
|
LET found = 1
|
|
END IF
|
|
MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient)))
|
|
END IF
|
|
END FOR
|
|
IF found == 1 THEN
|
|
LET mailbody[nl] = mailbody[nl].append("\r")
|
|
LET nl = nl + 1
|
|
END IF
|
|
|
|
MB(mailbody, nl, SFMT("Subject: %1\r", subject))
|
|
|
|
# Now add the message content
|
|
# Multipart message not handled
|
|
# Should check the body is really 7bit and not line starts with ., ...
|
|
# To be conformant with 7bit encoding. See RFC for details.
|
|
|
|
# \n.\n is the DATA section end mark.
|
|
# Should be replaced by \n..\n
|
|
LET sbmt = base.StringBuffer.create()
|
|
CALL sbmt.append(mailtext)
|
|
|
|
# Need to do it twice to properly handle following case: \n.\n.\n.\n.\n.\n
|
|
CALL sbmt.replace("\n.\n", "\n..\n", 0)
|
|
CALL sbmt.replace("\n.\n", "\n..\n", 0)
|
|
LET mailtext=sbmt.toString()
|
|
IF attachments.getLength() == 0 THEN
|
|
MB(mailbody, nl, SFMT("Content-Type: text/plain; charset=%1\r", "ISO-8859-1"))
|
|
MB(mailbody, nl, SFMT("Content-Transfer-Encoding: %1\r", "7bit"))
|
|
|
|
MB(mailbody, nl, "\r")
|
|
MB(mailbody, nl, mailtext)
|
|
MB(mailbody, nl, "\r")
|
|
ELSE
|
|
MB(mailbody, nl, "Content-Type: multipart/mixed;\r")
|
|
MB(mailbody, nl, ' boundary="' || boundary || '"\r')
|
|
MB(mailbody, nl, "\r")
|
|
MB(mailbody, nl, "This is a multi-part message in MIME format.\r")
|
|
|
|
# Add message text
|
|
MB(mailbody, nl, "--" || boundary || "\r")
|
|
MB(mailbody, nl, SFMT("Content-Type: text/plain; charset=%1; format=flowed\r", "ISO-8859-1"))
|
|
MB(mailbody, nl, SFMT("Content-Transfer-Encoding: %1\r", "7bit"))
|
|
MB(mailbody, nl, "\r")
|
|
MB(mailbody, nl, mailtext)
|
|
MB(mailbody, nl, '\r')
|
|
|
|
FOR i = 1 TO attachments.getLength()
|
|
MB(mailbody, nl, "--" || boundary || "\r")
|
|
|
|
LET extension = os.Path.extension(attachments[i].fname)
|
|
LET mimeid = getmime(extension)
|
|
IF mimeid == -1 THEN
|
|
# Unknown extension.
|
|
# Default to application/octet-stream, base64 encoded
|
|
LET mimeid = 1
|
|
END IF
|
|
CASE ext2mime[mimeid].encoding
|
|
WHEN "base64"
|
|
MB(mailbody, nl, SFMT("Content-Type: %1;\r", ext2mime[mimeid].mime))
|
|
MB(mailbody, nl, ' name="'|| attachments[i].fname || '"\r')
|
|
MB(mailbody, nl, 'Content-Transfer-Encoding: base64\r')
|
|
MB(mailbody, nl, 'Content-Disposition: inline;\r')
|
|
MB(mailbody, nl, ' filename="'|| os.Path.basename(attachments[i].fname) || '"\r')
|
|
MB(mailbody, nl, '\r')
|
|
|
|
LET nl = file_to_base64(attachments[i].sname, mailbody, nl)
|
|
WHEN "7bit"
|
|
MB(mailbody, nl, SFMT("Content-Type: %1;\r", ext2mime[mimeid].mime))
|
|
MB(mailbody, nl, ' name="'|| attachments[i].fname || '"\r')
|
|
MB(mailbody, nl, 'Content-Transfer-Encoding: 7bit\r')
|
|
MB(mailbody, nl, 'Content-Disposition: inline;\r')
|
|
MB(mailbody, nl, ' filename="'|| os.Path.basename(attachments[i].fname) || '"\r')
|
|
MB(mailbody, nl, '\r')
|
|
|
|
LET nl = file_to_7bit(attachments[i].sname, mailbody, nl)
|
|
# OTHERWISE
|
|
# CALL mbox_ok("Attachment encoding", SFMT("Unsupported encoding %1.", ext2mime[mimeid].encoding), "stop")
|
|
EXIT PROGRAM 1
|
|
END CASE
|
|
END FOR
|
|
MB(mailbody, nl, "--" || boundary || "--\r")
|
|
END IF
|
|
|
|
# Add end of message marker
|
|
MB(mailbody, nl, ".\r")
|
|
END FUNCTION
|
|
|
|
|
|
# Convert a file to a base64 string
|
|
REPORT gen_base64(l)
|
|
DEFINE l BYTE
|
|
OUTPUT
|
|
PAGE LENGTH 1
|
|
TOP MARGIN 0
|
|
BOTTOM MARGIN 0
|
|
LEFT MARGIN 0
|
|
FORMAT
|
|
ON EVERY ROW
|
|
PRINTX name=base64_l l
|
|
END REPORT
|
|
|
|
# Convert a file to 7bit encoding (fake)
|
|
FUNCTION file_to_7bit(fname, mailbody, nl)
|
|
DEFINE fname STRING
|
|
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
|
DEFINE ac base.Channel
|
|
DEFINE line STRING
|
|
DEFINE nl INTEGER
|
|
|
|
LET ac = base.Channel.create()
|
|
CALL ac.openFile(fname, "rb")
|
|
WHILE NOT ac.isEof()
|
|
LET line = ac.readLine()
|
|
MB(mailbody, nl, line)
|
|
END WHILE
|
|
CALL ac.close()
|
|
RETURN nl
|
|
END FUNCTION
|
|
|
|
FUNCTION smtpSend(ch, command)
|
|
DEFINE ch base.Channel
|
|
DEFINE command STRING
|
|
|
|
DEFINE smtpStatus INTEGER
|
|
DEFINE smtpMessage STRING
|
|
|
|
CALL clientWrite(ch, command)
|
|
CALL readSmtpAnswer(ch) RETURNING smtpStatus, smtpMessage
|
|
|
|
RETURN smtpStatus, smtpMessage
|
|
END FUNCTION
|
|
|
|
FUNCTION extract_email(email)
|
|
DEFINE email STRING
|
|
DEFINE s, e INTEGER
|
|
|
|
LET s = email.getIndexOf("<", 1)
|
|
LET e = email.getIndexOf(">", 1)
|
|
IF s == 0 OR e == 0 OR s >= e THEN
|
|
RETURN email
|
|
END IF
|
|
RETURN email.subString(s + 1, e - 1)
|
|
END FUNCTION
|
|
|
|
# Returns the mime entry index for the extension `ext'
|
|
# -1 if not entry matching the extension is found.
|
|
FUNCTION getmime(ext)
|
|
DEFINE ext STRING
|
|
DEFINE j INTEGER
|
|
FOR j = 1 TO ext2mime.getLength()
|
|
IF ext2mime[j].ext == ext THEN
|
|
RETURN j
|
|
END IF
|
|
END FOR
|
|
RETURN -1
|
|
END FUNCTION
|
|
|
|
FUNCTION file_to_base64(fname, mailbody, nl)
|
|
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
|
DEFINE fname STRING
|
|
DEFINE nl INTEGER
|
|
DEFINE l BYTE
|
|
DEFINE lfn STRING
|
|
DEFINE doc om.DomDocument
|
|
DEFINE node om.DomNode
|
|
DEFINE lnode om.NodeList
|
|
|
|
LET lfn = fname || ".xml"
|
|
|
|
LOCATE l IN FILE fname
|
|
CALL fgl_report_set_document_handler(om.XmlWriter.createFileWriter(lfn))
|
|
START REPORT gen_base64 TO FILE "dummy_report_file"
|
|
OUTPUT TO REPORT gen_base64(l)
|
|
FINISH REPORT gen_base64
|
|
|
|
# Load the generated XML file
|
|
LET doc = om.DomDocument.createFromXmlFile(lfn)
|
|
LET node = doc.getDocumentElement()
|
|
LET lnode = node.selectByTagName("Item")
|
|
LET node = lnode.item(1)
|
|
MB(mailbody, nl, node.getAttribute("value"))
|
|
# IF NOT os.Path.delete(lfn) THEN
|
|
# CALL mbox_ok("Warning", SFMT("The temporary file %1 could not be removed.", lfn), "stop")
|
|
# END IF
|
|
RETURN nl
|
|
END FUNCTION
|
|
|
|
FUNCTION readSmtpAnswer(ch)
|
|
DEFINE ch base.Channel
|
|
DEFINE line STRING
|
|
|
|
DEFINE smtpStatus INTEGER
|
|
DEFINE smtpMessage STRING
|
|
|
|
LET smtpMessage = ""
|
|
WHILE 1
|
|
LET line = serverRead(ch)
|
|
IF line IS NULL THEN
|
|
RETURN -1, "COULD NOT READ SMTP ANSWER"
|
|
END IF
|
|
IF line MATCHES "[0-9][0-9][0-9] *" THEN
|
|
IF smtpMessage.getLength() != 0 THEN
|
|
LET smtpMessage=smtpMessage || "\n"
|
|
END IF
|
|
LET smtpMessage=smtpMessage.append(line.subString(4, line.getLength()))
|
|
LET smtpStatus = line.subString(1,3)
|
|
RETURN smtpStatus, smtpMessage
|
|
END IF
|
|
IF line MATCHES "[0-9][0-9][0-9]-*" THEN
|
|
IF smtpMessage.getLength() != 0 THEN
|
|
LET smtpMessage=smtpMessage || "\n"
|
|
END IF
|
|
LET smtpMessage=smtpMessage.append(line.subString(4, line.getLength()))
|
|
END IF
|
|
END WHILE
|
|
END FUNCTION
|
|
|
|
FUNCTION clientWrite(mc, line)
|
|
DEFINE mc base.Channel
|
|
DEFINE line STRING
|
|
CALL smtplog.append('<FONT COLOR="#339933"><B>C> </B>')
|
|
CALL smtplog.append(line)
|
|
CALL smtplog.append("<BR></FONT>")
|
|
LET logtext=smtplog.toString()
|
|
CALL mc.writeLine(line)
|
|
DISPLAY "log:> ",line
|
|
END FUNCTION
|
|
|
|
FUNCTION serverRead(mc)
|
|
DEFINE line STRING
|
|
DEFINE mc base.Channel
|
|
LET line = mc.readLine()
|
|
CALL smtplog.append("<FONT COLOR=red><B>S> </B>")
|
|
CALL smtplog.append(line)
|
|
CALL smtplog.append("<BR></FONT>")
|
|
LET logtext=smtplog.toString()
|
|
RETURN line
|
|
END FUNCTION
|
|
|
|
FUNCTION sendmail(rcpt, mailbody)
|
|
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
|
DEFINE rcpt DYNAMIC ARRAY OF STRING
|
|
|
|
DEFINE smtpStatus INTEGER
|
|
DEFINE smtpMessage STRING
|
|
|
|
DEFINE mc base.Channel
|
|
DEFINE i INTEGER
|
|
LET cnf.smtphost = 'smtp.office365.com'
|
|
LET cnf.smtpport = 587
|
|
LET cnf.hostname='smtp.office365.com'
|
|
LET CNF.senderemail = 'nominas@marmotech.com.do'
|
|
LET cnf.clave = 'Marmotech22'
|
|
LET cnf.cnxtimeout=10
|
|
|
|
LET smtplog = base.StringBuffer.create()
|
|
LET mc = base.Channel.create()
|
|
TRY
|
|
DISPLAY "INICIO ",current
|
|
CALL mc.openClientSocket(cnf.smtphost, cnf.smtpport, "ub", cnf.cnxtimeout)
|
|
CATCH
|
|
CALL fgl_winmessage("INFO","FALLO CONEXION","INFO")
|
|
# CALL mbox_ok("Socket error", "The connection to the SMTP Server failed.\n", "stop")
|
|
RETURN FALSE
|
|
END TRY
|
|
CALL readSmtpAnswer(mc) RETURNING smtpStatus, smtpMessage
|
|
CALL smtpSend(mc, "HELO " || cnf.hostname || "\r") RETURNING smtpStatus, smtpMessage
|
|
IF smtpStatus >= 400 THEN
|
|
CALL smtpError(mc, smtpMessage)
|
|
RETURN FALSE
|
|
END IF
|
|
CALL smtpSend(mc, "STARTTLS \r") RETURNING smtpStatus, smtpMessage
|
|
IF smtpStatus >= 400 THEN
|
|
CALL smtpError(mc, smtpMessage)
|
|
RETURN FALSE
|
|
END IF
|
|
|
|
CALL smtpSend(mc, cnf.senderemail CLIPPED ||" "|| cnf.clave CLIPPED) RETURNING smtpStatus, smtpMessage
|
|
IF smtpStatus >= 400 THEN
|
|
CALL smtpError(mc, smtpMessage)
|
|
RETURN FALSE
|
|
END IF
|
|
|
|
CALL smtpSend(mc, SFMT("MAIL FROM: %1\r", extract_email(cnf.senderemail))) RETURNING smtpStatus, smtpMessage
|
|
IF smtpStatus >= 400 THEN
|
|
CALL smtpError(mc, smtpMessage)
|
|
RETURN FALSE
|
|
END IF
|
|
DISPLAY "TERMINO ",current
|
|
FOR i = 1 TO rcpt.getLength()
|
|
CALL smtpSend(mc, rcpt[i]) RETURNING smtpStatus, smtpMessage
|
|
IF smtpStatus >= 400 THEN
|
|
CALL smtpError(mc, smtpMessage)
|
|
RETURN FALSE
|
|
END IF
|
|
END FOR
|
|
|
|
CALL smtpSend(mc, "DATA\r") RETURNING smtpStatus, smtpMessage
|
|
IF smtpStatus >= 400 THEN
|
|
CALL smtpError(mc, smtpMessage)
|
|
RETURN FALSE
|
|
END IF
|
|
FOR i = 1 TO mailbody.getLength()
|
|
CALL clientWrite(mc, mailbody[i])
|
|
END FOR
|
|
CALL readSmtpAnswer(mc) RETURNING smtpStatus, smtpMessage
|
|
IF smtpStatus >= 400 THEN
|
|
CALL smtpError(mc, smtpMessage)
|
|
RETURN FALSE
|
|
END IF
|
|
CALL smtpSend(mc, "QUIT\r") RETURNING smtpStatus, smtpMessage
|
|
IF smtpStatus >= 400 THEN
|
|
CALL smtpError(mc, smtpMessage)
|
|
RETURN FALSE
|
|
END IF
|
|
CALL mc.close()
|
|
RETURN TRUE
|
|
END FUNCTION
|
|
|
|
|
|
# Deletes temporary attachment files
|
|
FUNCTION clear_attachments(attachments)
|
|
DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD
|
|
DEFINE i INTEGER
|
|
|
|
FOR i = 1 TO attachments.getLength()
|
|
LET rv = os.Path.delete(attachments[i].sname)
|
|
END FOR
|
|
END FUNCTION
|
|
|
|
FUNCTION smtpError(mc, msg)
|
|
DEFINE msg STRING
|
|
DEFINE mc base.Channel
|
|
DEFINE smtpStatus INTEGER
|
|
DEFINE smtpMessage STRING
|
|
|
|
CALL mbox_ok("SMTP Error", msg, "stop")
|
|
CALL smtpSend(mc, "QUIT\r") RETURNING smtpStatus, smtpMessage
|
|
CALL mc.close()
|
|
END FUNCTION
|