Envoi de mail par injection de données avec DreamMail

omen999

Il existe de multiples façons d'envoyer des mails au moyen de scripts. Celle-ci utilise le client DreamMail.

- Titre : Envoi de mail par injection de données avec DreamMail

- Auteur : omen999

- Parution : 30 avril 2010

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2010 omen999. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Présentation

- II. Le script VBS

- III. Liens

I - Présentation

Cet exemple, écrit en VBScript, illustre une technique d'envoi de mails par injection des données du message dans la base de DreamMail, un client mail d'origine chinoise. Il tire parti des informations contenues dans une documentation précédemment publiée sur mon site. Ce script permet la création d'un message de type texte accompagné éventuellement d'un fichier au format zip, rtf, doc, pdf ou txt. Ce message est placé dans la boite d'envoi et sera traité par DreamMail de trois façons différentes :
- soit par envoi manuel de l'utilisateur.
- soit automatiquement si cette fonction est déjà activée dans le logiciel.
- soit à une date et heure déterminée si le champ mWaitToSendTime est correctement renseigné dans le script.

Par la même occasion, ce script fournit un exemple de compression de stream au moyen de la bibliothèque zlib[1] très largement utilisée par les programmeurs.

Il s'agit d'une preuve de concept et non d'un script véritablement utilisable faute d'interface graphique digne de ce nom. Son adaptation sous la forme d'une macro VBA ne présente aucune difficulté majeure.

II - Le script VBS

'*********************************************************************************************************
' SendDMail par omen999 - (omen999.developpez.com)
' envoi d'un mail par injection directe dans la base de données du client DreamMail
' v 2.2 (support composant Buffer- XStandard et zlib) DM v 4.6.5.5
' PoC (pas de gestion sérieuse des erreurs, pas d'interface gui)
'
' l'objet de ce script est d'envoyer un mail en insérant les données directement dans les fichiers de DM
' le mail est en mode texte mais avec possibilité de joindre une pièce au format zip, rtf, doc, pdf ou txt
' DM procèdera ensuite à son envoi de façon autonome et asynchrone
' deux étapes sont nécessaires :
' 1° création du fichier msg au format eml
' 2° insertion d'un nouvel enregistrement dans le fichier DmData.dao cible en utilisant un objet DAO
' cette version 2.2 vise à créer un message indiscernable de ceux définis par le logiciel
' pour des raisons de performances, les opérations de hashage md5 et d'encodage base64 sont confiées
' au composant Buffer (www.xstandard.com/en/documentation/xbuffer)
' la compression zlib à la bibliothèque ... zlib (www.zlib.net) et
' au composant DynamicWrapperX (omen999.developpez.com/tutoriels/vbs/DynamicWrapperX)
'*********************************************************************************************************
Option Explicit
'constantes DAO
'RecordsetTypeEnum
Const _
dbOpenTable = 1, _
dbOpenDynaset = 2, _
dbOpenSnapshot = 4, _
dbOpenForwardOnly = 8, _
dbOpenDynamic = 16
'RecordsetOptionEnum
Const _
dbAppendOnly = 8, _
dbConsistent = 32, _
dbDenyRead = 2, _
dbDenyWrite = 1, _
dbExecDirect = 2048, _
dbFailOnError = 128, _
dbForwardOnly = 256, _
dbInconsistent = 16, _
dbRunAsync = 1024, _
dbSeeChanges = 512, _
dbSQLPassThrough = 64
'RecordsetLockEditsEnum
Const _
dbPessimistic = 2, _
dbOptimistic = 3, _
dbOptimisticValue = 1, _
dbReadOnly = 4, _
dbOptimisticBatch = 5
'constantes zlib
Const _
Z_OK = 0, _
Z_DATA_ERROR = -3, _
Z_MEM_ERROR = -4, _
Z_BUF_ERROR = -5

Const _
Z_NO_COMPRESSION = 0, _
Z_BEST_SPEED = 1, _
Z_BEST_COMPRESSION = 9, _
Z_DEFAULT_COMPRESSION = -1

'définition tableaux
Dim aDay,aMonth,aMIME,aTMIME
aDay=Array("Sun","Mon","Tue","Wed","Thu","Fri","Sat")
aMonth=Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")
aMIME=Array("application/zip","application/rtf","application/msword","application/pdf","text/plain")

'définition variables
Dim oFso,oFile,oHeaderFile,oBuffer,oWrap
Dim oDmJet,oDmDb,oRecAc,oRecEm 'variables DAO
Dim dmBaseName,sFolderBase,sCountName,sFolder,sEmlFile,sFullFile,dDate,sTypeMime,sReply
Dim sDest,sCC,sBCC,sSujet,sExtAtName,sFullAtName,sAtName,sBody,bAttExist,sBoundaryStr,sHeader
Dim smTo,smToShowName,smToShowAddr,smCc,smCcShowName,smCcShowAddr,smBcc,smZipText,uLenZipBuffer,smZipHtml,sRep
Set oDmJet=CreateObject("DAO.DBEngine.36")
Set oFso = CreateObject("Scripting.FileSystemObject")
Set oBuffer=CreateObject("XStandard.Buffer")
Set oWrap=CreateObject("DynamicWrapperX")
'dynamicwrapperx gère indifféremment les conventions stdcall ou cdecl
oWrap.Register "zlib1.dll","compress2","i=pUsul","r=l"
oWrap.Register "zlib1.dll","compressBound","i=l","r=l"

Sub WriteMailHeader(sList, sField)
'génère le header et les champs destinataires à partir de sList où chaque item est séparé par une virgule
'sField prend les valeurs "To :" "CC :" ou "BCC :"
Dim aMail, iC, sName
aMail=Split(sList,",")
oFile.Write sField
For iC=0 to UBound(aMail)
sName = Left(aMail(iC),InStr(aMail(iC),"@")-1)
oFile.Write Chr(34) & sName & Chr(34) & "<" & aMail(iC) & ">"
If iC < UBound(aMail) Then oFile.Write ", "
Select Case sField
Case "To: " smTo = smTo & Chr(34) & sName & Chr(34) & "<" & aMail(iC) & ">"
smToShowName = smToShowName & sName
smToShowAddr = smToShowAddr & aMail(iC)
If iC < UBound(aMail) Then
smTo = smTo & ","
smToShowName = smToShowName & ","
smToShowAddr = smToShowAddr & ","
End If
Case "CC: " smCc = smCc & Chr(34) & sName & Chr(34) & "<" & aMail(iC) & ">"
smCcShowName = smCcShowName & sName
smCcShowAddr = smCcShowAddr & aMail(iC)
If iC < UBound(aMail) Then
smCc = smCc & ","
smCcShowName = smCcShowName & ","
smCcShowAddr = smCcShowAddr & ","
End If
Case "BCC: " smBcc = smBcc & Chr(34) & sName & Chr(34) & "<" & aMail(iC) & ">"
If iC < UBound(aMail) Then smBcc = smBcc & ","
End Select
Next
oFile.WriteLine
End Sub

'ouverture de la BD en mode partagé et lecture/écriture
dmBaseName=InputBox("Entrez le nom complet de la base de données (DmData.dao) :")
Set oDmDb=oDmJet.OpenDatabase(dmBaseName,False,False,"MS Access;pwd=@i_10ve_U&^@^Rapid_?")

'récupération des données du compte d'envoi
Do
sCountName=InputBox("Entrez le nom du compte à partir duquel le mail sera créé :")
If sCountName="" Then
oDmDb.Close
WScript.Quit
End If
Set oRecAc=oDmDb.OpenRecordset("SELECT * FROM Account WHERE aAccountDisplayName=" & Chr(34) & sCountName & Chr(34),dbOpenSnapshot)
Loop Until oRecAc.RecordCount > 0

'entrée des données du mail par l'utilisateur
sDest=InputBox("Adresse mail du ou des destinataires séparés par une virgule :")
sCC=InputBox("Adresse mail du ou des destinataires en copie séparés par une virgule :")
sBCC=InputBox("Adresse mail du ou des destinataires en copie cachée séparés par une virgule :")
sSujet=InputBox("Sujet du message")
sBody=InputBox("Texte du message")
sFullAtName=InputBox("Nom complet du fichier à joindre au message :")
bAttExist=False
If oFso.FileExists(sFullAtName) Then
sExtAtName=oFso.GetExtensionName(sFullAtName)
If sExtAtName="txt" Then sExtAtName="text"
aTMIME=Filter(aMIME,sExtAtName)
If IsArray(aTMIME) Then
sTypeMime=aTMIME(0)
sAtName=oFso.GetFileName(sFullAtName)
bAttExist=True
Else
MsgBox "Type MIME inconnu"
oRecAc.Close
oDmDb.Close
WScript.Quit
End If
End If

'création du fichier eml
dDate=Now
sFolder=Year(dDate) & Right("0" & Month(dDate),2)
'petite précaution...
sFolderBase=oFso.GetParentFolderName(dmBaseName)
If not oFso.FolderExists(sFolderBase & "\Data\" & oRecAc.Fields("aEmailAddress") & "\" & sFolder) Then
oFso.CreateFolder sFolderBase & "\Data\" & oRecAc.Fields("aEmailAddress") & "\" & sFolder
End If
'même s'il existe un hack sous vbs pour récupérer les millisecondes, j'ai préféré faire un random pour générer les 3 derniers caractères ;)
Randomize
sEmlFile=Right("0" & Day(dDate),2) & Right("0" & Hour(dDate),2) & Right("0" & Minute(dDate),2) & Right("0" & Second(dDate),2) & Left(Int(Rnd*100000),3)
sFullFile = sFolderBase & "\Data\" & oRecAc.Fields("aEmailAddress") & "\" & sFolder & "\" & sEmlFile
Set oFile=oFso.CreateTextFile(sFullFile & ".eml", True)
If IsNull(oRecAc.Fields("aReplyAddress")) Then
sReply=oRecAc.Fields("aEmailAddress")
Else
sReply=oRecAc.Fields("aReplyAddress")
End If
oFile.WriteLine "Reply-To: " & sReply
oFile.WriteLine "From: " & Chr(34) & oRecAc.Fields("aDisplayName") & Chr(34) & "<" & oRecAc.Fields("aEmailAddress") & ">"
WriteMailHeader sDest,"To: "
If sCC <>"" Then WriteMailHeader sCC,"CC: "
If sBCC <>"" Then WriteMailHeader sBCC,"BCC: "
oFile.WriteLine "Subject: " & sSujet
oFile.WriteLine "Date: " & aDay(Weekday(dDate)-1) & ", " & Day(dDate) & " " & aMonth(Month(dDate)-1) & " " & Year(dDate) & " " & _
FormatDateTime(dDate,3) & " +0100" 'timezone france ex : Wed, 17 Mar 2010 17:57:45 +0100
oFile.WriteLine "Message-Id: DreamMailOutBox"
oFile.WriteLine "MIME-Version: 1.0"
'si pièce jointe création d'une boundary string
If bAttExist Then
sBoundaryStr="----=_NextPart_" & Right(Year(dDate),2) & Right("0" & Month(dDate),2) & Left(sEmlFile,9) & Int(Rnd * 10000000000) & "_000"
oFile.WriteLine "Content-Type: multipart/mixed; " 'un espace en fin de ligne
oFile.WriteLine vbTab & "boundary=""" & sBoundaryStr & """"
Else
oFile.WriteLine "Content-Type: text/plain;"
oFile.WriteLine vbTab & "charset=""ISO-8859-1"""
End If
oFile.WriteLine "X-Priority: 3"
oFile.WriteLine "X-Mailer: DreamMail 4.6.5.5" & vbCrLf
If bAttExist Then
oFile.WriteLine "--" & sBoundaryStr
oFile.WriteLine "Content-Type: text/plain; " 'un espace en fin de ligne
oFile.WriteLine vbTab & "charset=""ISO-8859-1"""
oFile.WriteLine "Content-Transfer-Encoding: 8bit" & vbCrLf
End If

'pour le hash md5 du header
Set oHeaderFile=oFso.OpenTextFile(sFullFile & ".eml",1)
sHeader=oHeaderFile.ReadAll
oHeaderFile.Close

'fin du header écriture du corps du message
oFile.WriteLine sBody & vbCrLf

'écriture de la pièce attachée
If bAttExist Then
oFile.WriteLine "--" & sBoundaryStr
oFile.WriteLine "Content-Type: " & sTypeMime & "; " 'espace en fin de ligne
oFile.WriteLine vbTab & "name=""" & sAtName & """"
oFile.WriteLine "Content-Transfer-Encoding: base64"
oFile.WriteLine "Content-Disposition: attachment;"
oFile.WriteLine vbTab & "filename=""" & sAtName & """" & vbCrLf
oBuffer.Load sFullAtName
oFile.WriteLine oBuffer.Base64String & vbCrLf
oBuffer.Reset
oFile.WriteLine "--" & sBoundaryStr & "--" & vbCrLf
End If
oFile.Close

'pour terminer création d'un nouvel enregistrement dans la BD de DreamMail
Set oRecEm=oDmDb.OpenRecordSet("Email",dbOpenTable,0,dbOptimistic)
With oRecEm
.AddNew
.Fields("mAccountID")=oRecAc.Fields("Id")              'ID du compte d'où partira le message
.Fields("mMailBoxID")=2                                'boite non-envoyés
.Fields("mDisplayColor")=0                             'police couleur noire
.Fields("mSize")=oFso.GetFile(sFullFile & ".eml").Size 'taille du msg
.Fields("mPriority")=2                                 'normale 0:haute 4:basse
.Fields("mIsRead")=1                                   '0=non-lu 1=lu
.Fields("mReplyMailID")=0                              'défaut
.Fields("mFwMailID")=0                                 'défaut
.Fields("mIsFlag")=0                                   'défaut
.Fields("mMailType")=0                                 'défaut
.Fields("mIsAttrExists")=0                             'défaut
.Fields("mIsAskReadReply")=0                           'demande AR 0=non 1=oui
If .Fields("mIsAskReadReply")=0 Then
.Fields("mAskReadReplyAddress")=""                   'champ vierge si pas d'AR
Else
.Fields("mAskReadReplyAddress")=sReply               'adresse de retour d'AR
End If                                                 'j'ai choisi la même que l'adresse de réponse mais ce n'est pas obligatoire
.Fields("mMessageID")="DreamMailOutBox"                'valeur générique pour les msgs en attente
.Fields("mReferences")=""                              'champ facultatif contenu libre
'date du message
.Fields("mDate")=Year(dDate) & "-" & Right("0" & Month(dDate),2) & "-" & Right("0" & Day(dDate),2) & ", " & FormatDateTime(dDate,3)
'date de création effective dans la BD
.Fields("mCreateDate")=Year(dDate) & "-" & Right("0" & Month(dDate),2) & "-" & Right("0" & Day(dDate),2) & ", " & Time
.Fields("mCharset")="ISO-8859-15"                      'code charset (latin 9)
.Fields("mSubject")=sSujet                             'sujet
.Fields("mReplyTo")=sReply                             'adresse utilisée pour la réponse
'champ standard identifant l'expéditeur notez l'espace
'(décomposé dans les deux champs suivants)
.Fields("mFrom")="""" & oRecAc.Fields("aDisplayName") & """ <" & oRecAc.Fields("aEmailAddress") & ">"
.Fields("mFromShowName")=oRecAc.Fields("aDisplayName") 'nom affiché de l'expéditeur
.Fields("mFromShowAddr")=oRecAc.Fields("aEmailAddress")'adresse mail du compte expéditeur
.Fields("mTo")=smTo                                    'champ standard liste des destinataires
.Fields("mToShowName")=smToShowName                    'liste des noms destinataires
.Fields("mToShowAddr")=smToShowAddr                    'liste des adresses destinataires
If sCC <> "" Then
.Fields("mCc")=smCc                                  'champ standard liste des dests en copie
.Fields("mCcShowName")=smCcShowName                  'liste des noms dests en copie
.Fields("mCcShowAddr")=smCcShowAddr                  'liste des adresses dests en copie
End If
oBuffer.Write sHeader                                  'chargement du header dans l'objet Buffer
.Fields("mHeaderMD5")=oBuffer.MD5                      'valeur de hash de l'en-tête msg
oBuffer.Reset
If sBCC <> "" Then .Fields("mBcc")=smBcc               'champ standard liste des dests en copie cachée
.Fields("mWaitToSendTime")=""                          'date de l'envoi différé [IMPORTANT] la date doit avoir le format "AAAA-MM-JJ, hh:mm:ss"
.Fields("mDisplayDecodeType")=0                        'décodage auto
If bAttExist Then .Fields("mAttrList")=sAtName & "|" & oFso.GetFile(sFullAtName).Size 'liste des noms de fichiers joints avec leur taille en octets
.Fields("mDetail")=""                                  'commentaire associé au msg (facultatif)
'le champ mZipText est un blob qui contient un stream compressé zlib (mode best_speed) du corps du message
'ce champ est utilisé par le volet prévisualisation pour éviter de ralentir l'affichage
If Right(sBody,2) <> vbCrLf Then                       'le texte doit se terminer par un cr/lf
sBody = sBody & vbCrLf
End If
uLenZipBuffer=oWrap.compressBound(Len(sBody))          'taille à prévoir du buffer en sortie
smZipText=oWrap.Space(Round(uLenZipBuffer/2),"")       'uniquement valable pour les messages charset latin
sRep = ""
Select Case oWrap.compress2(smZipText, uLenZipBuffer, sBody, Len(sBody), Z_BEST_SPEED)
Case Z_MEM_ERROR
sRep = "Mémoire insuffisante"
Case Z_BUF_ERROR
sRep = "Taille insuffisante du buffer"
Case Z_DATA_ERROR
sRep = "Erreur pendant l'opération de compression"
End Select
If sRep <> "" Then
MsgBox sRep, vbExclamation, "Erreur Compression ZLib"
oRecEm.Close
oRecAc.Close
oDmDb.Close
WScript.Quit
End If
.Fields("mZipText").AppendChunk LeftB(smZipText,uLenZipBuffer) 'tronque la chaine à sa longueur utile
'même quand le msg est en mode texte, DM complète le champ mZipHtml avec un stream zlib correspondant à une chaine vide...
'soit les 8 octets suivants : x78 x01 x03 x00 x00 x00 x00 x01
smZipHtml=oWrap.Space(4,"") '4 unicode = 8 octets
'on place deux valeurs 32bits en tenant compte du format little endian...
oWrap.NumPut &h30178,smZipHtml,0,"u"
oWrap.NumPut &h1000000,smZipHtml,0 + 4,"u"
.Fields("mZipHtml").AppendChunk smZipHtml              'contenu zippé du corps msg en html (utilisé par le volet prévisualisation)
'nom complet du fichier eml
.Fields("mSourceSaveFile")="${DBPATH}\Data\" & oRecAc.Fields("aEmailAddress") & "\" & sFolder & "\" & sEmlFile & ".eml"
.Fields("mIsEditMail")=1                               'signale que le message a été édité (DM v 4.6.5.5)
.Update                                                'écriture du msg dans la BD
.Close
End With
oRecAc.Close
oDmDb.Close

III - Liens

Site zlib
Site composant Buffer (freeware)
page DynamicWrapperX