Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

ESMTP (authenticated) - send simple email

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • PBWin/PBCC ESMTP (authenticated) - send simple email

    ESMTP, for all intents and purposes of this post, is essentially just SMTP but with a simple authentication step at the beginning (known as SMTP-AUTH) which is often required these days, to help ensure to your ISP that you really are the valid sender / who you say you are. After the authentication check is complete the communications continue using regular SMTP protocol.

    Discussion here.

    For a standard non-authenticated SMTP-only version see here, although this demo also supports regular SMTP.

    This demo while small is fairly complete as it supports plain SMTP, ESMTP AUTH PLAIN, and ESMTP AUTH LOGIN (see %ESMTP_AUTH), although I haven't added CRAM-MD5 AUTH yet as my current mailserver doesnt support it, but it too is fairly easy to add detection & support for - see here for excellent info regarding AUTH. There is also DIGEST-MD5, although this doesn't seem very common compared to CRAM-MD5, AUTH PLAIN, and AUTH LOGIN.

    IMPORTANT: Some email servers accept all three authentication methods available in this demo, whereas others may only accept one or two, so be sure to test your mail server with all three %ESMTP_AUTH values (0,1,2). The usual approach is to use SMTP (no auth) first, unless authentication is required, in which case you can determine which types of authentication are supported by the response the server sends back to you after you send it "EHLO". (Or alternatively, you can simply try each method until one succeeds, but listening to the servers header response is the faster & preferred approach).

    Code:
    #COMPILE EXE
    
    '// Your mail server & mail account settings:
    $ESMTP_SERV = "mail.myisp.com" 'Mail server address
    $ESMTP_USER = "[email protected]"   'Mail account username
    $ESMTP_PASS = "myp4ssw0rd"     'Mail account password   [I][COLOR=DimGray](that actually is my real password, but i faked my username)[/COLOR][/I]
    %ESMTP_PORT = 25               'ESMTP port (usually the same as your SMTP server. If you're not sure try 25, or 587)
    %ESMTP_AUTH = 0                '0=SMTP:No AUTH, 1=ESMTP:AUTH PLAIN, 2=ESMTP:AUTH LOGIN
    '\\ End of settings
     
    FUNCTION Base64enc (sText AS STRING, sOut AS STRING) AS LONG   '// Base64 encode. Required for ESMTP (%ESMTP_AUTH=1or2) but not SMTP (%ESMTP_AUTH=0)
    #REGISTER NONE
    ! push ebp
    DIM ptr1 AS DWORD, ptr2 AS DWORD, dwLen AS DWORD
    ptr1 = STRPTR(sText):  ptr2 = STRPTR(sOut):  dwLen = LEN(sText)
     ! mov ecx, dwLen
     ! mov esi, ptr1
     ! mov edi, ptr2
     ! xor ebp,ebp
     ! call above_charset
    charset:
     ! db "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
     ! db "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"
     ! db "0","1","2","3","4","5","6","7","8","9","+","/"
    above_charset:
     ! pop ebx
     ! push edi
    main_loop:
     ! dec ecx
     ! js end_base64
     ! jnz not_one
     ! movzx edx,byte ptr [esi]
     ! bswap edx
     ! inc ecx
     ! inc ecx
     ! call encode_bytes
     ! mov ax, &h3D3D  '==
     ! stosw
     ! jmp end_base64
    not_one:
     ! dec ecx
     ! jnz not_two
     ! movzx edx,word ptr [esi]
     ! bswap edx
     ! xor ecx,ecx
     ! mov cl,3
     ! call encode_bytes
     ! mov al, &h3D '=
     ! stosb
     ! jmp end_base64
    not_two:
     ! dec ecx
     ! push ecx
     ! movzx edx,word ptr [esi]
     ! bswap edx
     ! mov dh,byte ptr [esi+2]
     ! xor ecx,ecx
     ! mov cl,4
     ! call encode_bytes
     ! pop ecx
     ! add esi,3
     ! jmp main_loop
    end_base64:
     ! pop ecx
     ! sub ecx,edi
     ! neg ecx
     ! pop ebp
     ! mov FUNCTION, ecx
     EXIT FUNCTION
    encode_bytes:
     ! cmp ebp,&h4C
     ! jnz no_crlf
     ! mov ax,&h0A0D
     ! stosw
     ! xor ebp,ebp
    no_crlf:
     ! xor eax,eax
     ! rol edx,6
     ! mov al,dl
     ! and al,&b00111111
     ! xlatb
     ! stosb
     ! inc ebp
     ! loop encode_bytes
     ! ret
    END FUNCTION
    
    
    FUNCTION SendEmail (sToName AS STRING, sToAddr AS STRING, sFromName AS STRING, sFromAddr AS STRING, sSubject AS STRING, sMessage AS STRING) AS LONG  '// Returns 0 if successful, or errorcode
        LOCAL hTCP AS DWORD, sLocalHost AS STRING, sBuf AS STRING, sAuth AS STRING
    
        hTCP = FREEFILE
        TCP OPEN PORT %ESMTP_PORT AT $ESMTP_SERV AS hTCP
        IF ERR THEN                     FUNCTION = -1: GOTO ErrSMTP
    
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "220" THEN FUNCTION = -2: GOTO ErrSMTP
    
        HOST NAME TO sLocalHost
        IF sLocalHost = "" THEN sLocalHost = "email"  '// ensure we have a name to send with the HELO/EHLO line
    
    '// AUTHENTICATION START
       'SMTP: No AUTH
       #IF %ESMTP_AUTH = 0
        TCP PRINT hTCP, "HELO " & sLocalHost
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "250" THEN FUNCTION = -3: GOTO ErrSMTP
    
       'ESMTP: AUTH PLAIN
       #ELSEIF %ESMTP_AUTH = 1
        TCP PRINT hTCP, "EHLO " & sLocalHost    '// EHLO (ESMTP) instead of HELO (SMTP)
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "250" THEN FUNCTION = -4: GOTO ErrSMTP
        ? "AUTH types supported = " & sBuf
    
        sAuth = SPACE$(4096)
        Base64enc(CHR$(0) & $ESMTP_USER & CHR$(0) & $ESMTP_PASS, sAuth)
        TCP PRINT hTCP, "AUTH PLAIN " & RTRIM$(sAuth)
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "235" THEN FUNCTION = -5: GOTO ErrSMTP
    
       'ESMTP: AUTH LOGIN
       #ELSEIF %ESMTP_AUTH = 2
        TCP PRINT hTCP, "EHLO " & sLocalHost    '// EHLO (ESMTP) instead of HELO (SMTP)
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "250" THEN FUNCTION = -6: GOTO ErrSMTP
        ? "AUTH types supported = " & sBuf
    
        TCP PRINT hTCP, "AUTH LOGIN"
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "334" THEN FUNCTION = -7: GOTO ErrSMTP
    
        sAuth = SPACE$(4096):   Base64enc($ESMTP_USER, sAuth)
        TCP PRINT hTCP, RTRIM$(sAuth)
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "334" THEN FUNCTION = -8: GOTO ErrSMTP
    
        sAuth = SPACE$(4096):   Base64enc($ESMTP_PASS, sAuth)
        TCP PRINT hTCP, RTRIM$(sAuth)
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "235" THEN FUNCTION = -9: GOTO ErrSMTP
     
       #ELSE 'Invalid %ESMTP_AUTH value (must be 0, 1, or 2)
       #ENDIF
    '\\ END OF AUTHENTICATION
       
       'Regular SMTP protocol from here on ...
    
        TCP PRINT hTCP, "MAIL FROM:" & sFromAddr
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "250" THEN FUNCTION = -10: GOTO ErrSMTP
    
        TCP PRINT hTCP, "RCPT TO: " & sToAddr
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "250" THEN FUNCTION = -11: GOTO ErrSMTP
    
        TCP PRINT hTCP, "DATA"
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "354" THEN FUNCTION = -12: GOTO ErrSMTP
    
        '// Beginning of email header
        TCP PRINT hTCP, "Date: "    + DATE$
        TCP PRINT hTCP, "From: "    + CHR$(34) & sFromName & CHR$(34) & " <" & sFromAddr & ">"
        TCP PRINT hTCP, "To: "      + CHR$(34) & sToName & CHR$(34) & " <" & sToAddr & ">"
        'TCP PRINT hTCP, "Cc: " '// use this field if you want to change a BCC'd email into a CC'd email
        TCP PRINT hTCP, "Subject: " + sSubject
        TCP PRINT hTCP, "X-Mailer: PB ESMTP client demo"
        TCP PRINT hTCP, ""
        '// Beginning of email body
        TCP PRINT hTCP, sMessage
        TCP PRINT hTCP, ""
        '\\ End of the email. Resuming SMTP...
        TCP PRINT hTCP, "."
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "250" THEN FUNCTION = -13: GOTO ErrSMTP
    
        TCP PRINT hTCP, "QUIT"
        TCP RECV hTCP, 4096, sBuf
        IF LEFT$(sBuf, 3) <> "221" THEN FUNCTION = -14: GOTO ErrSMTP
    
        TCP CLOSE hTCP
        EXIT FUNCTION
    
    ErrSMTP:
        TCP CLOSE hTCP
        ? "ERROR. Last msg from server = " & sBuf
    END FUNCTION
     
    
    FUNCTION PBMAIN () AS LONG
    LOCAL lSent AS LONG, sToAddr AS STRING, sFromAddr AS STRING, sToName AS STRING, sFromName AS STRING, sSubject AS STRING, sMessage AS STRING
    '// Compose email (To & From Email Addresses plus Display Names, Subject, and Message Body)
    sToAddr = "[email protected]":   sToName = "Alice Smith"
    sFromAddr = "[email protected]":      sFromName = "Bob Jones"
    sSubject = "My subject"
    sMessage = "Email body line 1" & $CRLF & "Line 2" & $CRLF & "Line 3, ESMTP_AUTH is " & FORMAT$(%ESMTP_AUTH)
    '// Try to send the email
    lSent = SendEmail(sToName, sToAddr, sFromName, sFromAddr, sSubject, sMessage)
    IF lSent = 0 THEN ? "SUCCESS! MAIL SENT" ELSE ? "ERROR CODE = " & STR$(lSent) 
    
    #IF %DEF(%PB_CC32)
     STDOUT "Done, press any key to continue...";: WAITKEY$
    #ENDIF
    END FUNCTION
    Send to multiple email addresses (ie. BCC and CC emails):
    To send to multiple email addresses (whether you want to BCC or CC) you simply send the "RCPT TO:" field over and over, once for each email address ... for which you should receive something like "250 recipient <[email protected]> ok" with each address.

    Essentially, every email starts out as a BCC - the only difference between BCC and CC is that a CC message includes in its header who it was emailed to (so if you don't include the CC part it will remain a BCC, ie "blind"). Sample CC:
    From: "From Me" <[email protected]>
    To: "Person One" <[email protected]>
    Cc: "Person Two" <[email protected]>, "Person Three" <[email protected]>

    Last edited by Gary Beene; 5 Aug 2014, 11:59 PM. Reason: added link to discussion thread
    -

  • #2
    Check which AUTH types your ESMTP server supports...

    To find out which forms of AUTH your ESMTP email server provides (ie. LOGIN, or PLAIN, or CRAM-MD5 etc), you simply tell it "EHLO name" upon connection (as opposed to "HELO name" for basic SMTP).

    The mailserver then responds with something like this (multi-line due to CRLFs, but usually in the one network packet):
    250-8BITMIME
    250-SIZE 31457280
    250-STARTTLS
    250-AUTH PLAIN LOGIN
    250 AUTH=PLAIN LOGIN

    In the above example we can see it supports both "PLAIN" and "LOGIN" versions of AUTH, but not others such as "CRAM-MD5".

    ---

    Here is a simple way to check which ESMTP AUTH flavors your mail server supports, based on such data:
    Code:
    #COMPILE EXE
    
    SUB ESMTP_GetAuthTypes(sServ AS STRING, BYVAL dwPort AS DWORD)
     LOCAL hTCP AS DWORD, sBuf AS STRING, sLocalHost AS STRING, i AS DWORD, i2 AS DWORD
     hTCP = FREEFILE
     TCP OPEN PORT dwPort AT sServ AS hTCP
     IF ERR THEN ? "Error - couldn't connect to " & sServ & ":" & FORMAT$(dwPort):  EXIT SUB
    
     TCP RECV hTCP, 4096, sBuf
     IF LEFT$(sBuf, 3) <> "220" THEN
         ? "Error - connected but didn't receive protocol header"
         TCP CLOSE hTCP: EXIT SUB
     END IF
    
     HOST NAME TO sLocalHost
     IF sLocalHost = "" THEN sLocalHost = "email"  '// ensure we have a name to send with the EHLO line
    
     TCP PRINT hTCP, "EHLO " & sLocalHost          '// EHLO (ESMTP) instead of HELO (SMTP)
     TCP RECV hTCP, 4096, sBuf
     TCP CLOSE hTCP
    
     IF LEFT$(sBuf, 3) <> "250" THEN ? "EHLO error.": EXIT SUB
     i = INSTR(1, sBuf, "250 AUTH=")
     sBuf = MID$(sBuf, i, i2 - i)
     IF LEFT$(sBuf, 3) <> "250" THEN ? "AUTH error.": EXIT SUB
     i2 = INSTR(i, sBuf, $LF)
     ? "Server AUTH types: " & sBuf
    
     '// Show which AUTH types are or are not supported (normally you'd enum all words in the string, but for this demo I just check for the main types):
     IF INSTR(sBuf, "PLAIN") THEN ? "AUTH PLAIN supported" ELSE ? "AUTH PLAIN is NOT supported"
     IF INSTR(sBuf, "LOGIN") THEN ? "AUTH LOGIN supported" ELSE ? "AUTH LOGIN is NOT supported"
     IF INSTR(sBuf, "CRAM-MD5") THEN ? "AUTH CRAM-MD5 supported" ELSE ? "AUTH CRAM-MD5 is NOT supported"
     IF INSTR(sBuf, "DIGEST-MD5") THEN ? "AUTH DIGEST-MD5 supported" ELSE ? "AUTH DIGEST-MD5 is NOT supported"
     IF INSTR(sBuf, "GSSAPI") THEN ? "AUTH GSSAPI supported" ELSE ? "AUTH GSSAPI is NOT supported"
    
    END SUB
    
    
    FUNCTION PBMAIN () AS LONG
     ESMTP_GetAuthTypes ("mail.myisp.com", 25)    '// mail server tcp port (usually 25 or 587)
    
     #IF %DEF(%PB_CC32)
      STDOUT "Done, press any key to continue...";: WAITKEY$
     #ENDIF
    END FUNCTION
    Last edited by Wayne Diamond; 7 Aug 2014, 11:44 PM.
    -

    Comment


    • #3
      Test for availability of both SMTP and ESMTP support ...

      This needs to be done in TWO separate connections, as 1) many servers will reply with error "503 You already said HELO/EHLO" if you try both HELO and EHLO in the one connection (although some servers allow it), 2) it's possible for two separate server daemons to be running (one for SMTP, one for ESMTP), rather than just the one handling both, and 3) it's possible that they both may be using different TCP ports.

      Code:
      #COMPILE EXE
      
      FUNCTION Test_MailProtocol (sServ AS STRING, BYVAL dwPort AS DWORD, BYVAL dwESMTP AS DWORD) AS DWORD
       LOCAL hTCP AS DWORD, sBuf AS STRING, sLocalHost AS STRING
       hTCP = FREEFILE
       TCP OPEN PORT dwPort AT sServ AS hTCP
       IF ERR THEN ? "Error - couldn't connect to " & sServ & ":" & FORMAT$(dwPort):  EXIT FUNCTION
      
       TCP RECV hTCP, 4096, sBuf
       IF LEFT$(sBuf, 3) <> "220" THEN
           ? "Error - connected but didn't receive correct protocol header"
           TCP CLOSE hTCP: EXIT FUNCTION
       END IF
      
       HOST NAME TO sLocalHost
       IF sLocalHost = "" THEN sLocalHost = "email"  '// ensure we have a name to send with the HELO/EHLO line
      
       IF dwESMTP = 1 THEN
           TCP PRINT hTCP, "EHLO " & sLocalHost  '// ESMTP
       ELSE
           TCP PRINT hTCP, "HELO " & sLocalHost  '// SMTP
       END IF
       TCP RECV hTCP, 4096, sBuf
       TCP CLOSE hTCP
       IF LEFT$(sBuf, 3) = "250" THEN
           FUNCTION = 1
       ELSE
           ? "Error msg from server = " & sBuf
       END IF
      END FUNCTION
      
      
      FUNCTION PBMAIN () AS LONG
       IF Test_MailProtocol ("mail.myisp.com", 25, 0) = 1 THEN ? "SMTP success!" ELSE ? "SMTP failed!"
       IF Test_MailProtocol ("mail.myisp.com", 25, 1) = 1 THEN ? "ESMTP success!" ELSE ? "ESMTP failed!"   'port is usually 25 or 587
       
       #IF %DEF(%PB_CC32)
        STDOUT "Done, press any key to continue...";: WAITKEY$
       #ENDIF
      END FUNCTION
      Last edited by Wayne Diamond; 6 Aug 2014, 01:51 AM.
      -

      Comment

      Working...
      X