Jump to content

SMTP server example


Recommended Posts

I wanted to download a portable eMail server but didn't find one.

Spoiler
#NoTrayIcon

; https://www.autoitscript.com/forum/topic/212373-smtp-server-example/

; ..like an eMail catcher of sorts. It logs what it gets. That is all it does.
; No users, no passwords, just send to this and it'll say, "ok: got it".

; PS: did change the code around a bit to make it work in v3.2.12.1 of AutoIt
;      just in case you ( or I ) need to run this in something old.
;     Should run from Win2000 to Win11, but I just tested in Win11


Global $g_iListenSocket = 0, $g_SMTP_NowTime = tStamp('.', '_', '.')
Global $g_AppName = StringTrimRight(@ScriptName, 4)
Global $g_DumpsPath = @ScriptDir & '\SMTP_Dumps'
If Not FileExists($g_DumpsPath) Then DirCreate($g_DumpsPath)
Global $g_iCountGot = 0, $_g__Socket = 0
Global $g_Compiled = StringRight(@ScriptName, 4) = ".exe"
Global $g_InScite = StringInStr($CmdLineRaw, '/ErrorStdOut')
Global $g_ini = StringTrimRight(@ScriptFullPath, 4) & ".ini"
Global $g_RoundTripTimer = TimerInit()
If $CmdLine[0] Then
    DoCmdLine()
    If @extended Then Exit
EndIf
Func DoCmdLine()
    For $n = 1 To $CmdLine[0]
        Switch $CmdLine[$n]
            Case "/test"
                Exit
            Case "/msgbox"
                If $n < $CmdLine[0] And Int(IniRead($g_ini, "Settings", "DoMsgbox", "1")) Then
                    ; ..some day may continue with it
                EndIf
                Exit
        EndSwitch
    Next
EndFunc   ;==>DoCmdLine
Opt("TrayMenuMode", 3)
Opt("TrayOnEventMode", 1)
TraySetClick(16)
TCPStartup()
$g_iListenSocket = TCPListen("0.0.0.0", 25, 100)
If @error Then
    Console_Write('! Could not listen: ' & __WinAPI_GetErrorMessage(@error) & @CRLF)
    Exit 5
EndIf
main()
Func main()
    TrayCreateItem("Open this folder")
    TrayItemSetOnEvent(-1, "Open_this_folder")
    TrayCreateItem("")
    TrayCreateItem("Reset icon")
    TrayItemSetOnEvent(-1, "Reset_icon")
    TrayCreateItem("")
    TrayCreateItem("Restart")
    TrayItemSetOnEvent(-1, "RestartScript")
    TrayCreateItem("")
    TrayCreateItem("Exit")
    TrayItemSetOnEvent(-1, "ExitScript")
    TraySetOnEvent(-13, "On_EVENT_PRIMARYDOUBLE")
    TraySetState(1)
    TraySetIcon(@ScriptDir & "\" & $g_AppName & ".ico")
    TraySetToolTip($g_AppName & @CR & 'Count: ' & $g_iCountGot & @CR & @CR & @CR & ' ')
    While (1)
        smtp_server_ReadyForNextConnection()
        $g_iCountGot += 1
        TraySetIcon(@ScriptDir & "\" & $g_AppName & "_Red.ico")
        TraySetToolTip($g_AppName & @CR & 'Count: ' & $g_iCountGot & @CR & @CR & @CR & ' ')
    WEnd
EndFunc   ;==>main
Func OnAutoItExit()
    $_g__Socket = 0
EndFunc   ;==>OnAutoItExit
Func Open_this_folder()
    ShellExecute($g_DumpsPath)
EndFunc   ;==>Open_this_folder
Func On_EVENT_PRIMARYDOUBLE()
    Open_this_folder()
    Reset_icon()
EndFunc   ;==>On_EVENT_PRIMARYDOUBLE
Func Reset_icon()
    TraySetIcon(@ScriptDir & "\" & $g_AppName & "_Yellow.ico")
    Sleep(200)
    TraySetIcon(@ScriptDir & "\" & $g_AppName & ".ico")
    $g_iCountGot = 0
    TraySetToolTip($g_AppName & @CR & 'Count: ' & $g_iCountGot & @CR & @CR & @CR & ' ')
EndFunc   ;==>Reset_icon
Func RestartScript()
    TraySetState(2)
    OnAutoItExit()
    ShellExecute(@AutoItExe, _Iif($g_Compiled, '', '"' & @ScriptFullPath & '"'))
    Exit
EndFunc   ;==>RestartScript
Func _Iif($fTest, $vTrueVal, $vFalseVal, Const $_iCallerError = @error, Const $_iCallerExtended = @extended)
    If $fTest Then Return SetError($_iCallerError, $_iCallerExtended, $vTrueVal)
    Return SetError($_iCallerError, $_iCallerExtended, $vFalseVal)
EndFunc   ;==>_Iif
Func ExitScript()
    Exit
EndFunc   ;==>ExitScript
Func tStamp($sSepDate = "/", $sSepSep = " ", $sSepTime = ":", $iSepMSec = ".")
    Return @YEAR & $sSepDate & StringRight('00' & @MON, 2) & $sSepDate & StringRight('00' & @MDAY, 2) & $sSepSep & StringRight('00' & @HOUR, 2) & $sSepTime & StringRight('00' & @MIN, 2) & $sSepTime & StringRight('00' & @SEC, 2) & $iSepMSec & StringRight('000' & TimerInit(), 3)
EndFunc   ;==>tStamp
Func smtp_server_ReadyForNextConnection()
    ; Coded loosely based on this example:
    ; https://datatracker.ietf.org/doc/html/rfc3461#section-10.2
    Local $sReceived, $iSocket, $iDoneWithThis = 0, $sEmail = "", $hTimer = TimerInit()
    Do
        $iSocket = TCPAccept($g_iListenSocket)
        If @error Then
            $iError = @error
            Console_Write("! Server:" & @CRLF & "Could not accept the incoming connection, Error code: " & $iError & @CRLF & __WinAPI_GetErrorMessage($iError) & @CRLF & @CRLF)
            Return 1
        EndIf
        Sleep(50)
    Until $iSocket <> -1
    $g_RoundTripTimer = TimerInit()
    $g_SMTP_NowTime = tStamp('.', '_', '.')
    TCP_Send($hTimer, $iSocket, "220 Trap-SMTP says hello" & @CRLF)
    Do
        Sleep(10)
        $aCmd = TCP_GetCmd($hTimer, $iSocket)
        If @error Then Console_Write('! ""(' & 110 & ',0) - ' & @error & ',' & @extended & @CRLF)
        Switch $aCmd[0]
            Case "ehlo"
                TCP_Send($hTimer, $iSocket, "421 ERROR" & @CRLF)
                If @error Then Console_Write('! ""(' & 114 & ',0) - ' & @error & ',' & @extended & @CRLF)
            Case "helo"
                TCP_Send($hTimer, $iSocket, "250 DSN" & @CRLF)
                If @error Then Console_Write('! ' & 117 & ' - ' & @error & ',' & @extended & @CRLF)
            Case "mail", "rcpt"
                TCP_Send($hTimer, $iSocket, "250 okay" & @CRLF)
                If @error Then Console_Write('! ' & 120 & ' - ' & @error & ',' & @extended & @CRLF)
            Case "data"
                TCP_Send($hTimer, $iSocket, "354 send message" & @CRLF)
                If @error Then Console_Write('! ' & 123 & ' - ' & @error & ',' & @extended & @CRLF)
                $sEmail = TCP_GetData($hTimer, $iSocket)
                If @error Then Console_Write('! ' & 125 & ' - ' & @error & ',' & @extended & @CRLF)
                TCP_Send($hTimer, $iSocket, "250 message received" & @CRLF)
                If @error Then Console_Write('! ' & 127 & ' - ' & @error & ',' & @extended & @CRLF)
            Case "quit"
                TCP_Send($hTimer, $iSocket, "221 goodbye" & @CRLF)
                If @error Then Console_Write('! ' & 130 & ' - ' & @error & ',' & @extended & @CRLF)
                $iDoneWithThis = 1
        EndSwitch
        If TimerDiff($hTimer) > 2000 Then ExitLoop
    Until $iDoneWithThis
    TCPCloseSocket($iSocket)
    FileWrite($g_DumpsPath & "\smtp_" & $g_SMTP_NowTime & ".eml", $sEmail)
    FileWriteLine($g_DumpsPath & "\smtp_" & $g_SMTP_NowTime & ".txt", eml_GetGist($sEmail))
    Console_Write("+ Total time spent: " & TimerDiff($g_RoundTripTimer) & " ms. " & @CRLF)
EndFunc   ;==>smtp_server_ReadyForNextConnection
Func TCP_GetData(ByRef $hTimer, ByRef $iSocket)
    $hTimer = TimerInit()
    Local $sReceived, $sReceivedBuff = "", $iError = 0
    Do
        $sReceived = TCPRecv($iSocket, 4096)
        If @error Then
            $iError = 1
            ExitLoop
        EndIf
        If TimerDiff($hTimer) > 10000 Then
            $iError = 2
            ExitLoop
        EndIf
        If $sReceived = "" Then
            Sleep(20)
            ContinueLoop
        EndIf
        $hTimer = TimerInit()
        $sReceivedBuff &= $sReceived
        Console_Write("< GetData: Received:" & @CRLF & "  -----------8<-----------" & @CRLF & $sReceived & @CRLF & "  ----------->8-----------" & @CRLF)
    Until StringRight($sReceivedBuff, 5) = @CRLF & "." & @CRLF
    Return SetError($iError, StringLen($sReceivedBuff), $sReceivedBuff)
EndFunc   ;==>TCP_GetData
Func TCP_GetCmd(ByRef $hTimer, ByRef $iSocket)
    $hTimer = TimerInit()
    Local $sReceived, $aRet[2], $iError = 0
    Do
        $sReceived = TCPRecv($iSocket, 4096)
        If @error Then
            $iError = 1
            ExitLoop
        EndIf
        If TimerDiff($hTimer) > 1000 Then
            $iError = 2
            ExitLoop
        EndIf
        If $sReceived = "" Then
            Sleep(20)
            ContinueLoop
        EndIf
        $hTimer = TimerInit()
        Console_Write("< GetCmd: Received: >" & StringReplace($sReceived, @CRLF, "\r\n") & "<" & @CRLF)
    Until $sReceived <> ""
    $aRet[1] = $sReceived
    $sReceived = StringTrimRight($sReceived, 2)
    Local $iPos = StringInStr($sReceived, " ")
    If Not $iPos Then
        $aRet[0] = $sReceived
    Else
        $aRet[0] = StringLeft($sReceived, StringInStr($sReceived, " ") - 1)
    EndIf
    Return SetError($iError, StringLen($aRet[0]), $aRet)
EndFunc   ;==>TCP_GetCmd
Func TCP_Send(ByRef $hTimer, ByRef $iSoc, $sStr, $iLine = 193)
    Sleep(10)
    $hTimer = TimerInit()
    Local $iSent = TCPSend($iSoc, $sStr)
    Console_Write('> (' & $iLine & ',' & @error & ',' & $iSent & ') Send: >' & StringReplace($sStr, @CRLF, "\r\n") & '<' & @CRLF)
    Return SetError(@error, $iSent, $iSent)
EndFunc   ;==>TCP_Send
Func eml_GetGist($sFileContent)
    Local $ib64 = 0, $sb64 = "", $sTypeB64, $aArray = StringSplit($sFileContent, @CRLF, 1)
    Local $s_Subject, $s_From, $s_To, $s_Date, $s_Log = ""
    For $n = 1 To $aArray[0]
        If $ib64 And StringInStr($aArray[$n], "--===============") Then
            If $sTypeB64 = "plain" Then
                $s_Log &= @CRLF & BinaryToString(_Base64Decode_v2($sb64), 8) & @CRLF
                ExitLoop
            EndIf
            $ib64 = 0
        EndIf
        If StringInStr($aArray[$n], "Subject: ") = 1 Then $s_Log &= $aArray[$n] & @CRLF
        If StringInStr($aArray[$n], "From: ") = 1 Then $s_Log &= $aArray[$n] & @CRLF
        If StringInStr($aArray[$n], "To: ") = 1 Then $s_Log &= $aArray[$n] & @CRLF
        If StringInStr($aArray[$n], "Date: ") = 1 Then $s_Log &= $aArray[$n] & @CRLF
        If StringInStr($aArray[$n], "Content-Type: ") = 1 Then
            $sTypeB64 = _Iif(StringInStr($aArray[$n], "html"), "html", "plain")
        EndIf
        If StringInStr($aArray[$n], "Content-Transfer-Encoding: base64") Then
            $ib64 = 1
            $sb64 = ""
            ContinueLoop
        EndIf
        If $ib64 Then $sb64 &= $aArray[$n]
    Next
    Return $s_Log
EndFunc   ;==>eml_GetGist
Func _Base64Decode_v2($Data)
    Local $Opcode = "0xC81000005356578365F800E8500000003EFFFFFF3F3435363738393A3B3C3DFFFFFF00FFFFFF000102030405060708090A0B0C0D0E0F10111213141516171819FFFFFFFFFFFF1A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132338F45F08B7D0C8B5D0831D2E9910000008365FC00837DFC047D548A034384C0750383EA033C3D75094A803B3D75014AB00084C0751A837DFC047D0D8B75FCC64435F400FF45FCEBED6A018F45F8EB1F3C2B72193C7A77150FB6F083EE2B0375F08A068B75FC884435F4FF45FCEBA68D75F4668B06C0E002C0EC0408E08807668B4601C0E004C0EC0208E08847018A4602C0E00624C00A46038847028D7F038D5203837DF8000F8465FFFFFF89D05F5E5BC9C21000"
    Local $CodeBuffer = DllStructCreate("byte[" & BinaryLen($Opcode) & "]")
    DllStructSetData($CodeBuffer, 1, $Opcode)
    Local $Ouput = DllStructCreate("byte[" & BinaryLen($Data) & "]")
    Local $Ret = DllCall("user32.dll", "int", "CallWindowProc", "ptr", DllStructGetPtr($CodeBuffer), "str", $Data, "ptr", DllStructGetPtr($Ouput), "int", 0, "int", 0)
    Return BinaryMid(DllStructGetData($Ouput, 1), 1, $Ret[0])
EndFunc   ;==>_Base64Decode_v2
Func Console_Write($sStr, Const $_iCallerError = @error, Const $_iCallerExtended = @extended)
    If $g_InScite Then ConsoleWrite($sStr)
    FileWriteLine($g_DumpsPath & "\smtp_" & $g_SMTP_NowTime & ".log", $sStr)
    Return SetError($_iCallerError, $_iCallerExtended, 0)
EndFunc   ;==>Console_Write
Func __WinAPI_GetErrorMessage($iCode, $iLanguage = 0, Const $_iCallerError = @error, Const $_iCallerExtended = @extended)
    Local $FORMAT_MESSAGE_FROM_SYSTEM = 4096, $FORMAT_MESSAGE_IGNORE_INSERTS = 512
    Local $aCall = DllCall('kernel32.dll', 'dword', 'FormatMessageW', 'dword', BitOR($FORMAT_MESSAGE_FROM_SYSTEM, $FORMAT_MESSAGE_IGNORE_INSERTS), 'ptr', 0, 'dword', $iCode, 'dword', $iLanguage, 'wstr', '', 'dword', 4096, 'ptr', 0)
    If @error Or Not $aCall[0] Then Return SetError(@error, @extended, '')
    Return SetError($_iCallerError, $_iCallerExtended, StringRegExpReplace($aCall[5], '[' & @LF & ',' & @CR & ']*\Z', ''))
EndFunc   ;==>__WinAPI_GetErrorMessage


So I wrote one that gets the eMail and ... nothing. It just changes the color of the tray icon.
My idea is like a SNMP (UDP) trap but for TCP. So you'd get the eMail alerts from the device ( TrueNAS in my case ) and ... keep'em there. Better than flooding you eMail box from your secured on-line eMail provider, when setup to send "info" instead of "warning" level information.

Again, this just keeps them as dumps. But it may be a good start for your own coding :)

Note: This accepts all that is thrown at it on port 25. For experimental local network use only.

I'll put the icons and AutoIt v3.2 compiled script along with the source in the downloads area.

Edited by argumentum

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...