AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Winsock 2 - SMTP/POP with AutoHotkey?

 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Ask for Help
View previous topic :: View next topic  
Author Message
daonlyfreez



Joined: 16 Mar 2005
Posts: 745
Location: Berlin

PostPosted: Mon Jan 29, 2007 4:02 pm    Post subject: Winsock 2 - SMTP/POP with AutoHotkey? Reply with quote

Hi all,

I took the client/server TCP scripts from Zed Gecko, and tried to change it to be able to send/retreive e-mail.

I'm having difficulties with the commands. Some commands are not accepted, throwing errors.

Please experiment and tell me your findings...

You need to adapt the server:port variables in the script



Code:

/* Example calls...

SMTP (port 25):

    HELO
    MAIL FROM:<someone@someserver.com>
    RCPT TO:<someone@someotherserver.com>
    DATA
    From: "Some One" <someone@someserver.com>
    To: <someone@someotherserver.com>
    Subject: Hi there, how are you?
    "Message body"
    .
    QUIT

POP (port 110)

    HELO
    USER someone@someserver.com
    PASS password
    STAT
    LIST
    TOP 1 10
    QUIT

IRC (port 6667)
   
    /list /list -*ahk*
    /join #channel
    hi there
    /quit

*/


Remote_Address_Org = localhost
; 127.0.0.1 ; 192.168.178.20 ; smtp.someserver.com ; pop.someserver.com

Remote_Port = 110 ; 25 ; 6667

; Get IP if domain
StringReplace, Remote_Address_Stripped, Remote_Address_Org, `., , A

If Remote_Address_Stripped is not number
{
  IPs := HostToIp(Remote_Address_Org)
  DllCall("Ws2_32\WSACleanup") ; always inlude this line after calling to release the socket connection
  if IPs <> -1 ; no error occurred
  {
    If IPs contains `n
    {
      StringSplit, anIP, IPs, `n
/*
      Gui, +AlwaysOnTop +Owner
      Gui, 2: Add, Text, , Multiple IPs found! Select IP to use.
      Gui, 2: Add, ListBox, vIPSelected, a||b|c
      Gui, 2: Add, Button, x100 gIPCancel, Cancel
      Gui, 2: Show, w300 h200, Multiple IPs!
      Return
*/

; using first ip found
      Remote_Address := anIP1
    }
    Else
      Remote_Address := IPs
  }
  else
  {
     MsgBox, 16, Error!, Host "%Remote_Address_Org%" not found`, Exiting...
     Gosub, ExitSub
  }
}
Else
  Remote_Address := Remote_Address_Org



Gui, Add, Button, x10 y10 w60 h20 gConnection_Init, Connect
Gui, Add, Text, x80 y14 w300 h20, with domain: %Remote_Address_Org%. IP = %remote_address%:%remote_port%
Gui, Add, Button, x450 y10 w60 h20 gClear, Clear
Gui, Add, Edit, x10 y40 w500 R10 vMyEdit
Gui, Add, Edit, x10 y190 w320 R1 vToSend
Gui, Add, Button, x340 y190 w160 h20 gSendCommand, Send Command to Server
Gui, Add, Button, x10 y220 w80 h20 gSMTPHelp, SMTP Help
Gui, Add, Button, x120 y220 w80 h20 gPOPHelp, POP Help
Gui, Add, Button, x450 y240 w60 h20 gExitSub, Exit
Gui, Show, , AutoHotkey - Winsock 2 - SMTP/POP Test

LinesReceived:=0
Return

; --------------------------

IPOk:

Return

IPCancel:

Return

SMTPHelp:
  MsgBox, 64, SMTP Help - RFC 821,
  (LTrim Join`n
    HELO <SP> <domain> <CRLF>
    MAIL <SP> FROM:<reverse-path> <CRLF>
    RCPT <SP> TO:<forward-path> <CRLF>
    DATA <CRLF>
    RSET <CRLF>
    SEND <SP> FROM:<reverse-path> <CRLF>
    SOML <SP> FROM:<reverse-path> <CRLF>
    SAML <SP> FROM:<reverse-path> <CRLF>
    VRFY <SP> <string> <CRLF>
    EXPN <SP> <string> <CRLF>
    HELP [<SP> <string>] <CRLF>
    NOOP <CRLF>
    QUIT <CRLF>
    TURN <CRLF>
  )
Return

POPHelp:
  MsgBox, 64, POP Help - RFC1225,
  (LTrim Join`n
    USER <SP> <string> <CRLF>
    PASS <SP> <string> <CRLF>
    STAT <CRLF>
    LIST [<SP> <msgid>] <CRLF>
    DELE <SP> <msgid> <CRLF>
    TOP <SP> <msgid> <SP> <linecount> <CRLF>
    RETR <SP> <msgid> <CRLF>
    QUIT <CRLF>
    NOOP <CRLF>
    RPOP <SP> <string> <CRLF>
  )
Return

Connection_Init:
OnExit, ExitSub  ; For connection cleanup purposes.

; set up a very basic server:
;localsocket := PrepareForIncomingConnection(Network_Address, Network_Port)
;if localsocket = -1  ; Connection failed (it already displayed the reason).
;    ExitApp

; Connect to any type of server:
remotesocket := ConnectToAddress(Remote_Address, Remote_Port)
if remotesocket = -1  ; Connection failed (it already displayed the reason).
    ExitApp

; Find this script's main window:
Process, Exist  ; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts).
DetectHiddenWindows On
ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel)
DetectHiddenWindows Off

; When the OS notifies the script that there is incoming data waiting to be received,
; the following causes a function to be launched to read the data:
NotificationMsg = 0x5555  ; An arbitrary message number, but should be greater than 0x1000.
OnMessage(NotificationMsg, "ReceiveData")

; Set up the connection to notify this script via message whenever new data has arrived.
; This avoids the need to poll the connection and thus cuts down on resource usage.
FD_READ = 1     ; Received when data is available to be read.
FD_CLOSE = 32   ; Received when connection has been closed.
if DllCall("Ws2_32\WSAAsyncSelect", "UInt", remotesocket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_READ|FD_CLOSE)
{
    MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
    ExitApp
}

Loop ; Wait for incomming connections
{
; accept requests that are in the pipeline of the socket   
   conectioncheck := DllCall("Ws2_32\accept", "UInt", remotesocket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
; Ws2_22/accept returns the new Connection-Socket if a connection request was in the pipeline
; on failure it returns an negative value
    if conectioncheck > 1
    {
       MsgBox Incoming connection accepted
       break   
   }
    sleep 500 ;500 ; wait half 1 second then accept again
}   


return

Clear:
ShowReceived=
LinesReceived:=0   
GuiControl,, MyEdit,
return

SendCommand:
GuiControlGet, sendData, , ToSend
;msgbox % "|" senddata "|"
SendData(remotesocket,SendData . "`r`n")
Return

ConnectToAddress(IPAddress, Port)
; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success.
{
    VarSetCapacity(wsaData, 32)  ; The struct is only about 14 in size, so 32 is conservative.
    result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002)
    ; Since WSAStartup() will likely be the first Winsock function called by this script,
    ; check ErrorLevel to see if the OS has Winsock 2.0 available:
    if ErrorLevel
    {
        MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
        return -1
    }
    if result  ; Non-zero, which means it failed (most Winsock functions return 0 upon success).
    {
        MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    AF_INET = 2
    SOCK_STREAM = 1
    IPPROTO_TCP = 6
    socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
    if socket = -1
    {
        MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    ; Prepare for connection:
    SizeOfSocketAddress = 16
    VarSetCapacity(SocketAddress, SizeOfSocketAddress)
    InsertInteger(2, SocketAddress, 0, AF_INET)   ; sin_family
    InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2)   ; sin_port
    InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4)   ; sin_addr.s_addr

    ; Attempt connection:
    if DllCall("Ws2_32\connect", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
    {
        MsgBox % "connect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
        return -1
    }
    return socket  ; Indicate success by returning a valid socket ID rather than -1.
}

PrepareForIncomingConnection(IPAddress, Port)
; This can connect to most types of TCP servers, not just Network.
; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success.
{
    VarSetCapacity(wsaData, 32)  ; The struct is only about 14 in size, so 32 is conservative.
    result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002)
    ; Since WSAStartup() will likely be the first Winsock function called by this script,
    ; check ErrorLevel to see if the OS has Winsock 2.0 available:
    if ErrorLevel
    {
        MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
        return -1
    }
    if result  ; Non-zero, which means it failed (most Winsock functions return 0 upon success).
    {
        MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    AF_INET = 2
    SOCK_STREAM = 1
    IPPROTO_TCP = 6
    socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
    if socket = -1
    {
        MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    ; Prepare for connection:
    SizeOfSocketAddress = 16
    VarSetCapacity(SocketAddress, SizeOfSocketAddress)
    InsertInteger(2, SocketAddress, 0, AF_INET)   ; sin_family
    InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2)   ; sin_port
    InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4)   ; sin_addr.s_addr

    ; Bind to socket:
    if DllCall("Ws2_32\bind", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
    {
        MsgBox % "bind() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
        return -1
    }
    if DllCall("Ws2_32\listen", "UInt", socket, "UInt", "SOMAXCONN")
    {
        MsgBox % "LISTEN() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
        return -1
    }
   
    return socket  ; Indicate success by returning a valid socket ID rather than -1.
}

SendData(wParam,SendData, Repeat=1, Delay=0)
{
   socket := wParam
;   SendDataSize := VarSetCapacity(SendData)
;   SendDataSize += 1
   Loop % Repeat
   {
        SendIt := SendData . "(" . A_Index . ")"
      SendDataSize := VarSetCapacity(SendIt)
      SendDataSize += 1
     
      sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendIt, "Int", strlen(SendIt), "Int", 0)
      WinsockError := DllCall("Ws2_32\WSAGetLastError")
      if WinsockError <> 0 ; WSAECONNRESET, which happens when Network closes via system shutdown/logoff.
         ; Since it's an unexpected error, report it.  Also exit to avoid infinite loop.
         MsgBox % "send() indicated Winsock error " . WinsockError
        sleep,Delay
   }       
   
;send( sockConnected,> welcome, strlen(welcome) + 1, NULL);
}

ReceiveData(wParam, lParam)
; By means of OnMessage(), this function has been set up to be called automatically whenever new data
; arrives on the connection.
{
    Critical
    global ShowReceived
    global MyEdit
    global LinesReceived

    socket := wParam
    ReceivedDataSize = 4096  ; Large in case a lot of data gets buffered due to delay in processing previous data.
    VarSetCapacity(ReceivedData, ReceivedDataSize, 0)  ; 0 for last param terminates string for use with recv().

    ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", socket, "Str", ReceivedData, "Int", ReceivedDataSize, "Int", 0)
    if ReceivedDataLength = 0  ; The connection was gracefully closed,
        ExitApp  ; The OnExit routine will call WSACleanup() for us.
    if ReceivedDataLength = -1
    {
        WinsockError := DllCall("Ws2_32\WSAGetLastError")
        if WinsockError = 10035  ; WSAEWOULDBLOCK, which means "no more data to be read".
            return 1  ; Should probably never happen since we were notified there is data on the connection, yet now we're told there's none?
        if WinsockError <> 10054 ; WSAECONNRESET, which happens when Network closes via system shutdown/logoff.
            ; Since it's an unexpected error, report it.  Also exit to avoid infinite loop.
            MsgBox % "recv() indicated Winsock error " . WinsockError
        ExitApp  ; The OnExit routine will call WSACleanup() for us.
    }

    ; Since above didn't return or exit, process the data that was just received.
   Loop  ; For each binary-zero-delimited segment in the data.
   {
       Loop, parse, ReceivedData, `n, `r  ; For each line in this segment.
       {
          LinesReceived++               
           if (LinesReceived = 1) {
              ShowReceived = %LinesReceived%: %A_LoopField%
           } else {
              ShowReceived = %ShowReceived%`n%LinesReceived%: %A_LoopField%
          }
          ;Tooltip % ShowReceived
          GuiControl,, MyEdit, %ShowReceived%
       }
       ReceivedDataLengthApparent := strlen(ReceivedData)
       if (ReceivedDataLength-1 <= ReceivedDataLengthApparent)  ; -1 to adjust for the legitimate/last zero-termintor at the end of the last segment.
          break   ; No more binary-zero-delimited segements are present.
      ; Otherwise, there's a binary zero "hiding" more data that lies to its right.
      DllCall("RtlMoveMemory", str, ReceivedData  ; Shift the data leftward to eliminate from consideration the segement that was just processed.
         , UInt, &ReceivedData + ReceivedDataLengthApparent + 1
         , UInt, ReceivedDataLength - ReceivedDataLengthApparent)
      ReceivedDataLength -= ReceivedDataLengthApparent + 1  ; Adjust length to reflect actual NEW length of ReceivedData.
   }

    return 1  ; Tell the program that no further processing of this message is needed.
}

HostToIp(NodeName) ; returns -1 if unsuccessfull or a newline seperated list of valid IP addresses on success
{
   VarSetCapacity(wsaData, 32)  ; The struct is only about 14 in size, so 32 is conservative.
   result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002)
   if ErrorLevel   ; check ErrorLevel to see if the OS has Winsock 2.0 available:
   {
      MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
      return -1
   }
   if result  ; Non-zero, which means it failed (most Winsock functions return 0 on success).
   {
      MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") ; %
      return -1
   }
   PtrHostent := DllCall("Ws2_32\gethostbyname", str, Nodename)
   if (PtrHostent = 0)
      Return -1
   VarSetCapacity(hostent,16,0)
   DllCall("RtlMoveMemory",UInt,&hostent,UInt,PtrHostent,UInt,16) 
   h_name      := ExtractInteger(hostent,0,false,4)
   h_aliases   := ExtractInteger(hostent,4,false,4)
   h_addrtype  := ExtractInteger(hostent,8,false,2)
   h_length    := ExtractInteger(hostent,10,false,2)
   h_addr_list := ExtractInteger(hostent,12,false,4)
   ; Retrieve official name
   VarSetCapacity(Name,64,0)
   DllCall("RtlMoveMemory",UInt,&Name,UInt,h_name,UInt,64)
   ; Retrieve Aliases
   VarSetCapacity(Aliases,12,0)
   DllCall("RtlMoveMemory", UInt, &Aliases, UInt, h_aliases, UInt, 12)
   Loop, 3
   {
      offset := ((A_Index-1)*4)
      PtrAlias%A_Index% := ExtractInteger(Aliases,offset,false,4)
      If (PtrAlias%A_Index% = 0)
         break
      VarSetCapacity(Alias%A_Index%,64,0)
      DllCall("RtlMoveMemory",UInt,&Alias%A_Index%,UInt,PtrAlias%A_Index%,Uint,64)
   }
   VarSetCapacity(AddressList,12,0)
   DllCall("RtlMoveMemory",UInt,&AddressList,UInt,h_addr_list,UInt,12)
   Loop, 3
   {
      offset := ((A_Index-1)*4)
      PtrAddress%A_Index% := ExtractInteger(AddressList,offset,false,4)
      If (PtrAddress%A_Index% =0)
         break
      VarSetCapacity(address%A_Index%,4,0)
      DllCall("RtlMoveMemory" ,UInt,&address%A_Index%,UInt,PtrAddress%A_Index%,Uint,4)
      i := A_Index
      Loop, 4
      {
         if Straddress%i%
            Straddress%i% := Straddress%i% "." ExtractInteger(address%i%,(A_Index-1 ),false,1)
         else
            Straddress%i% := ExtractInteger(address%i%,(A_Index-1 ),false,1)
      }
      Straddress0 = %i%
   }
   loop, %Straddress0% ; put them together and return them
   {
      _this := Straddress%A_Index%
      if _this <>
         IPs = %IPs%%_this%
      if A_Index = %Straddress0%
         break
      IPs = %IPs%`n
   }
   return IPs
}

ExtractInteger(ByRef sr, o = 0, is = false, s = 4)
{
   Loop %s%
      r += *(&sr + o + A_Index-1) << 8*(A_Index-1)
   If (!is OR s > 4 OR r < 0x80000000)
      Return r
   Return -(0xFFFFFFFF - r + 1)
}

InsertInteger(i, ByRef d, o = 0, s = 4)
{
   Loop %s%
      DllCall("RtlFillMemory", "UInt", &d + o + A_Index-1, "UInt", 1, "UChar", i >> 8*(A_Index-1) & 0xFF)
}

GuiEscape:
guiclose:
ExitSub:  ; This subroutine is called automatically when the script exits for any reason.
; MSDN: "Any sockets open when WSACleanup is called are reset and automatically
; deallocated as if closesocket was called."
DllCall("Ws2_32\WSACleanup")
ExitApp



Edit: Updated code
_________________
(sorry, homesite offline atm)


Last edited by daonlyfreez on Mon Jan 29, 2007 10:18 pm; edited 1 time in total
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
bnbn2000



Joined: 10 Jul 2006
Posts: 4

PostPosted: Mon Jan 29, 2007 8:15 pm    Post subject: Reply with quote

give a try on CMD utility like blat/getmail ?
Back to top
View user's profile Send private message
daonlyfreez



Joined: 16 Mar 2005
Posts: 745
Location: Berlin

PostPosted: Mon Jan 29, 2007 9:57 pm    Post subject: Reply with quote

bnbn2000 wrote:
give a try on CMD utility like blat/getmail ?


Hi bnbn2000,

Well, I know and use blat/getmail already. Would be nice to have an AHK only solution.

These first attempts at directly using Winsock 2 could work (if they work), with any TCP/IP protocol, for example POP/SMTP, IRC... Cool

TCP/IP protocols

Btw... Script updated

Got POP/SMTP working on a local mailserver, trying IRC now...
_________________
(sorry, homesite offline atm)
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Ask for Help All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group