Code archives/Networking/Blitz E-Mail Library
This code has been declared by its author to be Public Domain code.
Download source code
| A collection of function which enable you to send MIME encoded e-mail. Features :- Send HTML and/or plain text mail with embedded images and multiple attachments. SASL authentication (CRAM-MD5, LOGIN and PLAIN). Includes Base64 encoder/decoder and MD5 functions. Please use this code responsibly! |
;******************************************************************************************** ;*** Blitz Mail Library ;*** Version 2.0 ;******************************************************************************************** ;Version 1.0 ;Release...Err...2001 sometime :o/ ;Version 1.1 ;Release 03/12/04 - Fixed multi Line response bug. ; Added Base64 decoder and SASL 'LOGIN' authentication. ;Version 2.0 ;Release 05/05/05 - Rewrote send_mail$(), it should be easier to follow and add stuff now. ; Added SASL 'CRAM-MD5' authentication. ; Added SASL 'PLAIN' authentication. ; Added the option of supplying a server port. Const AUTH_NONE = 0 Const AUTH_PLAIN = 1 Const AUTH_LOGIN = 2 Const AUTH_CRAMMD5 = 4 Const AUTH_ALL = AUTH_PLAIN Or AUTH_LOGIN Or AUTH_CRAMMD5 Const MAIL_DEBUG = False Dim MD5_k(63), MD5_r(15), MD5_data(1) init_MD5() SeedRnd MilliSecs() Type html_image Field filename$, cid$, idata$ End Type Type inc_data Field filename$, idata$ End Type ; Sends the processed mail. ; mailfrom$ = A string holding the senders mail address ; fromname$ = A string holding the senders name ; mailto$ = A string holding the recipients mail address ; toname$ = A string holding the recipients name - optional ; subject$ = A string holding the subject of the mail ; body$ = A string holding the processed MIME mail ; mailserver$ = A string holding the ESMTP/SMTP servers IP address or FQDN. ; You can add an optional port number (default = 25). ; example >>> "mail.myisp.net" Or "mail.myisp.net:25" ; username$ = A string holding the account username ; userpass$ = A string holding the account password ; This function returns a string containing ;success; if successful or an error message if not Function send_mail$(mailfrom$, fromname$, mailto$, toname$ = "", subject$, body$, mailserver$, username$="", userpass$="", auth_type = AUTH_NONE) Local server, code, resp$, client_ip$, ip_count, port If toname$ = "" Then toname$ = mailto$ ip_count = CountHostIPs("") If ip_count client_ip$ = "[" + DottedIP$(HostIP(1)) + "]" Else client_ip$ = "[127.0.0.1]" EndIf If Instr(mailserver$, ":") port = Right$(mailserver$, (Len(mailserver$) - (Instr(mailserver$, ":") + 1))) Else port = 25 EndIf server = OpenTCPStream(mailserver$, port) ;Is there a connection? If Not server Then Return "Failed to connect to `" + mailserver$ + "`" resp$ = get(server) ;Is the mail server available? Select get_code(resp$) Case 421 CloseTCPStream server Return "Service not available"; >>>>>> ERROR EXIT Case 220 ; Server available, lets get logged on. put(server, "EHLO " + client_ip$) resp$ = get(server) ;An ESMTP server? If get_code(resp$) = 250 ; If a username and password supplied, do SASL stuff. If (username$ <> "") And (userpass$ <> "") And (auth_type <> AUTH_NONE) Repeat Select True Case (auth_type And AUTH_CRAMMD5) ; Do CRAM-MD5 auth_type = auth_type And ~AUTH_CRAMMD5 put(server, "AUTH CRAM-MD5") ; CRAM-MD5? resp$ = get(server) If get_code(resp$) = 334 put(server, base64_enc(username$ + " " + cram_md5(userpass$, base64_dec(get_message(resp$))))) resp$ = get(server) If get_code(resp$) <> 235 CloseTCPStream server Return resp$ ; >>>>>> ERROR EXIT EndIf EndIf Case (auth_type And AUTH_LOGIN) ; Do LOGIN auth_type = auth_type And ~AUTH_LOGIN put(server, "AUTH LOGIN") ; LOGIN? resp$ = get(server) If get_code(resp$) = 334 While get_code(resp$) = "334" Select Lower$(base64_dec(get_message(resp$))) Case "username:" put(server, base64_enc(username$, 0)) resp$ = get(Server) Case "password:" put(server, base64_enc(userpass$, 0)) resp$ = get(Server) End Select Wend If get_code(resp$) <> 235 CloseTCPStream server Return resp$ ; >>>>>> ERROR EXIT EndIf EndIf Case (auth_type And AUTH_PLAIN) ; Do PLAIN auth_type = auth_type And ~AUTH_PLAIN put(server, "AUTH PLAIN") ; PLAIN? resp$ = get(server) If get_code(resp$) = 334 put(server, base64_enc(Chr$(0) + username$ + Chr$(0) + userpass$ + Chr$(0), 0)) resp$ = get(Server) If get_code(resp$) <> 235 CloseTCPStream server Return resp$ ; >>>>>> ERROR EXIT EndIf EndIf Case auth_type = AUTH_NONE CloseTCPStream server Return "Unsupported SASL Authorisation type" ; >>>>>> ERROR EXIT End Select Until get_code(resp$) = 235 EndIf Else put(server, "HELO " + client_ip$) resp$ = get(server) If get_code(resp$) <> 250 CloseTCPStream server Return resp$ ; >>>>>> ERROR EXIT EndIf EndIf Default CloseTCPStream server Return "Time Out" ; >>>>>> ERROR EXIT End Select ;Now we`re logged in, we can send mail put(server, "MAIL FROM: <" + mailfrom$ + ">") resp$ = get(server) If get_code(resp$) <> 250 CloseTCPStream server If get_code(resp$) = 501 Return "Email sender not specified (or invalid address)" ; >>>>>> ERROR EXIT Else Return resp$ ; >>>>>> ERROR EXIT EndIf EndIf put(server, "RCPT TO: <" + mailto$ + ">") resp$ = get(server) If get_code(resp$) <> 250 CloseTCPStream server If get_code(resp$) = 501 Return "Email recipient not specified (or invalid address)" ; >>>>>> ERROR EXIT Else Return resp$ ; >>>>>> ERROR EXIT EndIf EndIf put(server, "DATA") resp$ = get(server) If get_code(resp$) <> 354 CloseTCPStream server Return resp$ ; >>>>>> ERROR EXIT EndIf put(server, "From: " + qt$ + mailname$ + qt$ + " <" + mailfrom$ + ">") put(server, "To: " + qt$ + toname$ + qt$ + " <" + mailto$ + ">") put(server, "Subject: " + subject$) put(server, "X-Mailer: Blitz Mail Library v2.0") put(server, body$) put(server, ".") resp$ = get(server) If get_code(resp$) <> 250 CloseTCPStream server Return resp$ ; >>>>>> ERROR EXIT EndIf put(server, "QUIT") resp$ = get(server) If get_code(resp$) = 221 Return "success" ; >>>>>> SUCCESS EXIT Else Return resp$ ; >>>>>> ERROR EXIT EndIf End Function ; Encodes the processed HTML text and/or plain text into MIME format ; html$ = A string holding the processed HTML text - optional ; plain$= A string holding the plain text - optional ; This function returns a string containing the MIME mail body text ; ; You`re probably wondering why BOTH of the parameters are optional? ; If you wanted to send a blank mail with just an attachment. ; You still need to run this function to MIME encode the attachment - build_mime$() Function build_mime$(html$="", plain$="") Local out$, nl$ = Chr$(13) + Chr$(10), qt$ = Chr$(34) Local bound$ = "=_PART_00_" + unique() Local bound1$ = "=_PART_01_" + unique() Local bound2$ = "=_PART_02_" + unique() If plain$ = "" And html$ <> "" plain$ = "This is a HTML mail. Your mail app may not be able to display the main body of this mail." EndIf out$ = out$ + "MIME-Version: 1.0" + nl$ out$ = out$ + "Content-Type: multipart/mixed;" + nl$ + Chr$(9) out$ = out$ + "boundary=" + qt$ + bound$ + qt$ + nl$ + nl$ out$ = out$ + "This is a MIME encoded message" + nl$ + nl$ out$ = out$ + "--" + bound$ + nl$ If html$ <> "" If First html_image = Null out$ = out$ + "Content-Type: multipart/alternative;" + nl$ + Chr$(9) out$ = out$ + "boundary=" + qt$ + bound1$ + qt$ + nl$ + nl$ + nl$ out$ = out$ + "--" + bound1$ + nl$ out$ = out$ + "Content-Type: text/plain; charset=" + qt$ + "iso-8859-1" + qt$ + nl$ out$ = out$ + "Content-Transfer-Encoding: quoted-printable" + nl$ + nl$ out$ = out$ + qp_enc$(plain$) + nl$ out$ = out$ + "--" + bound1$ + nl$ out$ = out$ + "Content-Type: text/html; charset=" + qt$ + "iso-8859-1" + qt$ + nl$ out$ = out$ + "Content-Transfer-Encoding: quoted-printable" + nl$ + nl$ out$ = out$ + html$ + nl$ Else out$ = out$ + "Content-Type: multipart/related;" + nl$ + Chr$(9) out$ = out$ + "boundary=" + qt$ + bound1$ + qt$ + nl$ + nl$ + nl$ out$ = out$ + "--" + bound1$ + nl$ out$ = out$ + "Content-Type: multipart/alternative;" + nl$ + Chr$(9) out$ = out$ + "boundary=" + qt$ + bound2$ + qt$ + nl$ + nl$ + nl$ out$ = out$ + "--" + bound2$ + nl$ out$ = out$ + "Content-Type: text/plain; charset=" + qt$ + "iso-8859-1" + qt$ + nl$ out$ = out$ + "Content-Transfer-Encoding: quoted-printable" + nl$ + nl$ out$ = out$ + qp_enc$(plain$) + nl$ out$ = out$ + "--" + bound2$ + nl$ out$ = out$ + "Content-Type: text/html; charset=" + qt$ + "iso-8859-1" + qt$ + nl$ out$ = out$ + "Content-Transfer-Encoding: quoted-printable" + nl$ + nl$ out$ = out$ + html$ + nl$ out$ = out$ + "--" + bound2$ + "--" + nl$ + nl$ For image.html_image=Each html_image out$ = out$ + "--" + bound1$ + nl$ out$ = out$ + "Content-Type: image/" + Right$(image.html_image\filename$, 3) out$ = out$ + "; name=" + qt$ + image.html_image\filename$ + qt$ + nl$ out$ = out$ + "Content-Transfer-Encoding: base64" + nl$ out$ = out$ + "Content-ID: <" + image.html_image\cid$ + ">" + nl$ + nl$ out$ = out$ + image.html_image\idata$ + nl$ Next EndIf out$ = out$ + "--" + bound1$ + "--" + nl$ + nl$ Else out$ = out$ + "Content-Type: text/plain;" + nl$ + nl$ out$ = out$ + "Content-Transfer-Encoding: quoted-printable" + nl$ out$ = out$ + qp_enc$(plain$) + nl$ EndIf If First inc_data <> Null For inc.inc_data=Each inc_data out$ = out$ + "--" + bound$ + nl$ out$ = out$ + "Content-Type: application/octete-stream" out$ = out$ + "; name=" + qt$ + inc.inc_data\filename$ + qt$ + nl$ out$ = out$ + "Content-Transfer-Encoding: base64" + nl$ out$ = out$ + "Content-Disposition: attachment; filename=" + qt$ + inc.inc_data\filename$ + qt$ + nl$ + nl$ out$ = out$ + inc.inc_data\idata$ + nl$ Next EndIf out$ = out$ + "--" + bound$ + "--" + nl$ Delete Each html_image Delete Each inc_data Return out$ End Function ; Converts an external file into a string ; inp$ = A string containing the filepath to the required file ; This function returns a string containing the files data Function file_to_string$(inp$) Local file, out$ file = ReadFile(inp$) If (Not file) Then RuntimeError "Couldn't open '" + inp$ + "' for encoding." Repeat out$ = out$ + Chr$(ReadByte(file)) Until Eof(file) CloseFile(file) Return out$ End Function ; Encodes files as attachments ; file$ = A string containing the filepath to the required file Function process_include$(file$) inc.inc_data = New inc_data inc.inc_data\filename$ = file$ inc.inc_data\idata$ = base64_enc$(file_to_string$(file$)) End Function ; Scans the HTML string loads and encodes any images and replaces their filename with a ContentIDentifier ; inp$ = A string containing the HTML text ; dir$ = A string containing the path to the original HTML file - optional (if the HTML file is in the same directory ; as your code) ; This function returns a string containing the processed HTML text Function process_html$(inp$, dir$="") Local out$ = inp$, img$, ifrom, ito = 1, fnd If dir$ <> "" And Right$(dir$, 1) <> "\" Then dir$ = dir$ + "\" Repeat ifrom = Instr(Lower$(inp$), "<img", ito) If ifrom ifrom = Instr(Lower$(inp$), "src", ifrom + 4) + 3 ifrom = Instr(Lower$(inp$), Chr$(34), ifrom) + 1 ito = Instr(Lower$(inp$), Chr$(34), ifrom) fnd = False For image.html_image=Each html_image If image.html_image\filename$ = Mid$(inp$, ifrom, ito - ifrom) Then fnd = True Next If (Not fnd) image.html_image = New html_image image.html_image\filename$ = Mid$(inp$, ifrom, ito - ifrom) image.html_image\cid$ = unique$() image.html_image\idata$ = base64_enc$(file_to_string$(dir$ + image.html_image\filename$)) out$ = Replace$(out$, image.html_image\filename$, "cid:" + image.html_image\cid$) EndIf EndIf Until (Not ifrom) Return qp_enc$(out$) End Function ; Encodes a string using the base64 algorithm ; inp$ = A string containing the data to be encoded ; add_nl = 1 - adds a newline seguence at the end of the encoded string, 0 - no newline (if string is < 76 chars) ; This function returns a string containing the encoded data ; You shouldn`t need to call this directly. But feel free. Function base64_enc$(inp$, add_nl=1) Local b64_enc$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" Local nl$ = Chr$(13) + Chr$(10) Local out$, trp$, char, i = 1 Repeat trp$ = Mid$(inp$, i, 3) Select Len(trp$) Case 3 out$ = out$ + Mid$(b64_enc$, (Asc(Mid$(trp$, 1, 1)) Shr 2) + 1, 1) out$ = out$ + Mid$(b64_enc$, (((Asc(Mid$(trp$, 1, 1)) Shl 4) Or (Asc(Mid$(trp$, 2, 1)) Shr 4)) And $3f) + 1, 1) out$ = out$ + Mid$(b64_enc$, (((Asc(Mid$(trp$, 2, 1)) Shl 2) Or (Asc(Mid$(trp$, 3, 1)) Shr 6)) And $3f) + 1, 1) out$ = out$ + Mid$(b64_enc$, (Asc(Mid$(trp$, 3, 1)) And $3f) + 1, 1) Case 2 out$ = out$ + Mid$(b64_enc$, (Asc(Mid$(trp$, 1, 1)) Shr 2) + 1, 1) out$ = out$ + Mid$(b64_enc$, (((Asc(Mid$(trp$, 1, 1)) Shl 4) Or (Asc(Mid$(trp$, 2, 1)) Shr 4)) And $3f) + 1, 1) out$ = out$ + Mid$(b64_enc$, ((Asc(Mid$(trp$, 2, 1)) Shl 2) And $3f) + 1, 1) out$ = out$ + "=" Case 1 out$ = out$ + Mid$(b64_enc$, (Asc(Mid$(trp$, 1, 1)) Shr 2) + 1, 1) out$ = out$ + Mid$(b64_enc$, ((Asc(Mid$(trp$, 1, 1)) Shl 4) And $3f) + 1, 1) out$ = out$ + "==" End Select i = i + 3 char = char + 4 If char = 76 out$ = out$ + nl$ char = 0 EndIf Until i > Len(inp$) If char And add_nl Then out$ = out$ + nl$ Return out$ End Function ; Decodes a string that`s been encoded with the base64 algorithm ; inp$ = A string containing the encoded data ; This function returns a string containing the decoded data ; You shouldn`t need to call this directly. But feel free. Function base64_dec$(inp$) Local b64_enc$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" Local out$, oct, i = 1, qc, char Repeat char = Instr(b64_enc$, Mid$(inp$, i, 1)) If char > 0 If char = 65 Then char = 1 oct = (oct Shl 6) Or ((char - 1) And $3f) qc = qc + 1 EndIf If qc = 4 out$ = out$ + Chr$((oct Shr 16) And $ff) + Chr$((oct Shr 8) And $ff) + Chr$(oct And $ff) oct = 0 qc = 0 EndIf i = i + 1 Until i > Len(inp$) Return out$ End Function ; Encodes a string using the quoted printable algorithm ; inp$ = A string containing the text to be encoded ; This function returns a string containing the encoded text ; You shouldn`t need to call this directly. But feel free. Function qp_enc$(inp$) Local qp_asc$ = Chr$(9) + " !#$%&'()*+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~" Local nl$ = Chr$(13) + Chr$(10) Local out$, cnt, char$, i Repeat i = i + 1 char$ = Mid$(inp$, i, 1) If Instr(qp_asc$, char$) out$ = out$ + char$ cnt = cnt + 1 Else If Mid$(inp$, i, 2) <> nl$ If cnt > 72 out$ = out$ + "=" + nl$ cnt = 0 EndIf out$ = out$ + "=" + Right$(Hex$(Asc(char$)), 2) cnt = cnt + 3 Else out$ = out$ + nl$ i = i + 1 cnt = 0 EndIf EndIf If cnt = 75 out$ = out$ + "=" + nl$ cnt = 0 EndIf Until i >= Len(inp$) If cnt Then out$ = out$ + nl$ Return out$ End Function ; Spits out a 24 digit (96 bit) random(ish) hexadecimal string Function unique$() Local out$, i For i= 1 To 12 out$ = out$ + Right$(Hex$(Rand(1, 255)), 2) Next Return out$ End Function ; Write a string to the given stream with debug log output Function put(stream, out$) WriteLine stream, out$ If MAIL_DEBUG DebugLog "BLITZ : " + out$ EndIf End Function ; Read a string from the given stream with debug log output Function get$(stream) Local in$ in$ = ReadLine(stream) While Mid$(in$, 4, 1) = "-" If MAIL_DEBUG DebugLog "SERVER : " + in$ EndIf in$ = ReadLine(stream) Wend If MAIL_DEBUG DebugLog "SERVER : " + in$ EndIf Return in$ End Function ; The next two are pretty pointless, they just make the send_mail$() code easier to read. Function get_code(inp$) Return Left(inp$, 3) End Function Function get_message$(inp$) Return Right$(inp$, Len(inp$) - 4) End Function ; CRAM-MD5 and associated functions Function cram_md5$(key$, tag$) Local ikey$ If Len(key$) > 64 ikey$ = hex2char$(md5$(key$)) Else ikey$ = key$ + String(Chr$(0), 64 - Len(key$)) EndIf Return md5$(xor_string$(ikey$, $5C) + hex2char$(md5$(xor_string$(ikey$, $36) + tag$))) End Function Function xor_string$(a$, b) Local c, out$ For c=1 To Len(a$) out$ = out$ + Chr$(Asc(Mid$(a$, c, 1)) Xor b) Next Return out$ End Function Function hex2char$(hexin$) Local c, out$ For c=1 To Len(hexin$) Step 2 out$ = out$ + Chr$(hex2dec(Mid(hexin$, c, 2))) Next Return out$ End Function Function hex2dec(hexin$) Local c, dec, hexval$ = "0123456789ABCDEF" For c=1 To Len(hexin$) dec = (dec Shl 4) Or (Instr(hexval$, Upper$(Mid$(hexin$, c, 1))) - 1) Next Return dec End Function Function init_MD5() Local c Restore MD5_constdata For c=0 To 63 Read MD5_k(c) Next For c=0 To 15 Read MD5_r(c) Next End Function .MD5_constdata Data $D76AA478, $E8C7B756, $242070DB, $C1BDCEEE, $F57C0FAF, $4787C62A Data $A8304613, $FD469501, $698098D8, $8B44F7AF, $FFFF5BB1, $895CD7BE Data $6B901122, $FD987193, $A679438E, $49B40821, $F61E2562, $C040B340 Data $265E5A51, $E9B6C7AA, $D62F105D, $02441453, $D8A1E681, $E7D3FBC8 Data $21E1CDE6, $C33707D6, $F4D50D87, $455A14ED, $A9E3E905, $FCEFA3F8 Data $676F02D9, $8D2A4C8A, $FFFA3942, $8771F681, $6D9D6122, $FDE5380C Data $A4BEEA44, $4BDECFA9, $F6BB4B60, $BEBFBC70, $289B7EC6, $EAA127FA Data $D4EF3085, $04881D05, $D9D4D039, $E6DB99E5, $1FA27CF8, $C4AC5665 Data $F4292244, $432AFF97, $AB9423A7, $FC93A039, $655B59C3, $8F0CCC92 Data $FFEFF47D, $85845DD1, $6FA87E4F, $FE2CE6E0, $A3014314, $4E0811A1 Data $F7537E82, $BD3AF235, $2AD7D2BB, $EB86D391 Data 7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21 Function MD5$(in$) Local h0 = $67452301, h1 = $EFCDAB89, h2 = $98BADCFE, h3 = $10325476 Local chunkStart, intCount, i, a, b, c, d, f, t intCount = (((Len(in$) + 8) Shr 6) + 1) Shl 4 Dim MD5_data(intCount - 1) For c=0 To (Len(in$) - 1) MD5_data(c Shr 2) = MD5_data(c Shr 2) Or (Asc(Mid(in$, c + 1, 1)) Shl ((c And 3) Shl 3)) Next MD5_data(c Shr 2) = MD5_data(c Shr 2) Or ($80 Shl ((c And 3) Shl 3)) MD5_data(intCount - 2) = Len(in$) * 8 For chunkStart=0 To (intCount - 1) Step 16 a = h0 : b = h1 : c = h2 : d = h3 For i=0 To 15 f = d Xor (b And (c Xor d)) t = d d = c : c = b b = Rol((a + f + MD5_k(i) + MD5_data(chunkStart + i)), MD5_r(i And 3)) + b a = t Next For i=16 To 31 f = c Xor (d And (b Xor c)) t = d d = c : c = b b = Rol((a + f + MD5_k(i) + MD5_data(chunkStart + (((5 * i) + 1) And 15))), MD5_r((i And 3) + 4)) + b a = t Next For i=32 To 47 f = b Xor c Xor d t = d d = c : c = b b = Rol((a + f + MD5_k(i) + MD5_data(chunkStart + (((3 * i) + 5) And 15))), MD5_r((i And 3) + 8)) + b a = t Next For i=48 To 63 f = c Xor (b Or ~d) t = d d = c : c = b b = Rol((a + f + MD5_k(i) + MD5_data(chunkStart + ((7 * i) And 15))), MD5_r((i And 3) + 12)) + b a = t Next h0 = h0 + a : h1 = h1 + b h2 = h2 + c : h3 = h3 + d Next Dim MD5_data(1) Return Lower$(LEHex(h0) + LEHex(h1) + LEHex(h2) + LEHex(h3)) End Function Function Rol(val, shift) Return (val Shl shift) Or (val Shr (32 - shift)) End Function Function LEHex$(val) Local c, out$, hexval$ = Hex$(val) For c = 7 To 1 Step -2 out$ = out$ + Mid(hexval$, c, 2) Next Return out$ End Function |