1 /*
2  * Internet Messaging Transport Base Class
3  *
4  * Copyright 2006 Robert Shearman for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #define COBJMACROS
22 
23 #include "ws2tcpip.h"
24 #include "windef.h"
25 #include "winnt.h"
26 #include "objbase.h"
27 #include "ole2.h"
28 #include "mimeole.h"
29 
30 #include <stdio.h>
31 
32 #include "wine/debug.h"
33 
34 #include "inetcomm_private.h"
35 
36 WINE_DEFAULT_DEBUG_CHANNEL(inetcomm);
37 
38 static const WCHAR wszClassName[] = {'T','h','o','r','C','o','n','n','W','n','d','C','l','a','s','s',0};
39 
40 #define IX_READ     (WM_USER + 0)
41 #define IX_READLINE (WM_USER + 1)
42 #define IX_WRITE    (WM_USER + 2)
43 
44 HRESULT InternetTransport_Init(InternetTransport *This)
45 {
46     This->pCallback = NULL;
47     This->Status = IXP_DISCONNECTED;
48     This->Socket = -1;
49     This->fCommandLogging = FALSE;
50     This->fnCompletion = NULL;
51 
52     return S_OK;
53 }
54 
55 HRESULT InternetTransport_GetServerInfo(InternetTransport *This, LPINETSERVER pInetServer)
56 {
57     if (This->Status == IXP_DISCONNECTED)
58         return IXP_E_NOT_CONNECTED;
59 
60     *pInetServer = This->ServerInfo;
61     return S_OK;
62 }
63 
64 HRESULT InternetTransport_InetServerFromAccount(InternetTransport *This,
65     IImnAccount *pAccount, LPINETSERVER pInetServer)
66 {
67     FIXME("(%p, %p): stub\n", pAccount, pInetServer);
68     return E_NOTIMPL;
69 }
70 
71 HRESULT InternetTransport_Connect(InternetTransport *This,
72     LPINETSERVER pInetServer, boolean fAuthenticate, boolean fCommandLogging)
73 {
74     struct addrinfo *ai;
75     struct addrinfo *ai_cur;
76     struct addrinfo hints;
77     int ret;
78     char szPort[10];
79 
80     if (This->Status != IXP_DISCONNECTED)
81         return IXP_E_ALREADY_CONNECTED;
82 
83     This->ServerInfo = *pInetServer;
84     This->fCommandLogging = fCommandLogging;
85 
86     This->hwnd = CreateWindowW(wszClassName, wszClassName, 0, 0, 0, 0, 0, NULL, NULL, NULL, 0);
87     if (!This->hwnd)
88         return HRESULT_FROM_WIN32(GetLastError());
89     SetWindowLongPtrW(This->hwnd, GWLP_USERDATA, (LONG_PTR)This);
90 
91     hints.ai_flags          = 0;
92     hints.ai_family         = PF_UNSPEC;
93     hints.ai_socktype       = SOCK_STREAM;
94     hints.ai_protocol       = IPPROTO_TCP;
95     hints.ai_addrlen        = 0;
96     hints.ai_addr           = NULL;
97     hints.ai_canonname      = NULL;
98     hints.ai_next           = NULL;
99 
100     snprintf(szPort, sizeof(szPort), "%d", (unsigned short)pInetServer->dwPort);
101 
102     InternetTransport_ChangeStatus(This, IXP_FINDINGHOST);
103 
104     ret = getaddrinfo(pInetServer->szServerName, szPort, &hints, &ai);
105     if (ret)
106     {
107         ERR("getaddrinfo failed: %d\n", ret);
108         return IXP_E_CANT_FIND_HOST;
109     }
110 
111     for (ai_cur = ai; ai_cur; ai_cur = ai->ai_next)
112     {
113         int so;
114 
115         if (TRACE_ON(inetcomm))
116         {
117             char host[256];
118             char service[256];
119             getnameinfo(ai_cur->ai_addr, ai_cur->ai_addrlen,
120                 host, sizeof(host), service, sizeof(service),
121                 NI_NUMERICHOST | NI_NUMERICSERV);
122             TRACE("trying %s:%s\n", host, service);
123         }
124 
125         InternetTransport_ChangeStatus(This, IXP_CONNECTING);
126 
127         so = socket(ai_cur->ai_family, ai_cur->ai_socktype, ai_cur->ai_protocol);
128         if (so == -1)
129         {
130             WARN("socket() failed\n");
131             continue;
132         }
133         This->Socket = so;
134 
135         /* FIXME: set to async */
136 
137         if (0 > connect(This->Socket, ai_cur->ai_addr, ai_cur->ai_addrlen))
138         {
139             WARN("connect() failed\n");
140             closesocket(This->Socket);
141             continue;
142         }
143         InternetTransport_ChangeStatus(This, IXP_CONNECTED);
144 
145         /* FIXME: call WSAAsyncSelect */
146 
147         freeaddrinfo(ai);
148         TRACE("connected\n");
149         return S_OK;
150     }
151 
152     freeaddrinfo(ai);
153 
154     return IXP_E_CANT_FIND_HOST;
155 }
156 
157 HRESULT InternetTransport_HandsOffCallback(InternetTransport *This)
158 {
159     if (!This->pCallback)
160         return S_FALSE;
161 
162     ITransportCallback_Release(This->pCallback);
163     This->pCallback = NULL;
164 
165     return S_OK;
166 }
167 
168 HRESULT InternetTransport_DropConnection(InternetTransport *This)
169 {
170     if (This->Status == IXP_DISCONNECTED)
171         return IXP_E_NOT_CONNECTED;
172 
173     shutdown(This->Socket, SD_BOTH);
174 
175     closesocket(This->Socket);
176 
177     DestroyWindow(This->hwnd);
178     This->hwnd = NULL;
179 
180     InternetTransport_ChangeStatus(This, IXP_DISCONNECTED);
181 
182     return S_OK;
183 }
184 
185 HRESULT InternetTransport_GetStatus(InternetTransport *This,
186     IXPSTATUS *pCurrentStatus)
187 {
188     *pCurrentStatus = This->Status;
189     return S_OK;
190 }
191 
192 HRESULT InternetTransport_ChangeStatus(InternetTransport *This, IXPSTATUS Status)
193 {
194     This->Status = Status;
195     if (This->pCallback)
196         ITransportCallback_OnStatus(This->pCallback, Status,
197             (IInternetTransport *)&This->u.vtbl);
198     return S_OK;
199 }
200 
201 HRESULT InternetTransport_ReadLine(InternetTransport *This,
202     INETXPORT_COMPLETION_FUNCTION fnCompletion)
203 {
204     if (This->Status == IXP_DISCONNECTED)
205         return IXP_E_NOT_CONNECTED;
206 
207     if (This->fnCompletion)
208         return IXP_E_BUSY;
209 
210     This->fnCompletion = fnCompletion;
211 
212     This->cbBuffer = 1024;
213     This->pBuffer = HeapAlloc(GetProcessHeap(), 0, This->cbBuffer);
214     This->iCurrentBufferOffset = 0;
215 
216     if (WSAAsyncSelect(This->Socket, This->hwnd, IX_READLINE, FD_READ) == SOCKET_ERROR)
217     {
218         ERR("WSAAsyncSelect failed with error %d\n", WSAGetLastError());
219         /* FIXME: handle error */
220     }
221     return S_OK;
222 }
223 
224 HRESULT InternetTransport_Write(InternetTransport *This, const char *pvData,
225     int cbSize, INETXPORT_COMPLETION_FUNCTION fnCompletion)
226 {
227     int ret;
228 
229     if (This->Status == IXP_DISCONNECTED)
230         return IXP_E_NOT_CONNECTED;
231 
232     if (This->fnCompletion)
233         return IXP_E_BUSY;
234 
235     /* FIXME: do this asynchronously */
236     ret = send(This->Socket, pvData, cbSize, 0);
237     if (ret == SOCKET_ERROR)
238     {
239         ERR("send failed with error %d\n", WSAGetLastError());
240         /* FIXME: handle error */
241     }
242 
243     fnCompletion((IInternetTransport *)&This->u.vtbl, NULL, 0);
244 
245     return S_OK;
246 }
247 
248 HRESULT InternetTransport_DoCommand(InternetTransport *This,
249     LPCSTR pszCommand, INETXPORT_COMPLETION_FUNCTION fnCompletion)
250 {
251     if (This->Status == IXP_DISCONNECTED)
252         return IXP_E_NOT_CONNECTED;
253 
254     if (This->fnCompletion)
255         return IXP_E_BUSY;
256 
257     if (This->pCallback && This->fCommandLogging)
258     {
259         ITransportCallback_OnCommand(This->pCallback, CMD_SEND, (LPSTR)pszCommand, 0,
260             (IInternetTransport *)&This->u.vtbl);
261     }
262     return InternetTransport_Write(This, pszCommand, strlen(pszCommand), fnCompletion);
263 }
264 
265 static LRESULT CALLBACK InternetTransport_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
266 {
267     if (uMsg == IX_READ)
268     {
269         InternetTransport *This = (InternetTransport *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
270 
271         /* no work to do */
272         if (!This->fnCompletion)
273             return 0;
274 
275         while (This->iCurrentBufferOffset < This->cbBuffer)
276         {
277             if (recv(This->Socket, &This->pBuffer[This->iCurrentBufferOffset], 1, 0) <= 0)
278             {
279                 if (WSAGetLastError() == WSAEWOULDBLOCK)
280                     break;
281 
282                 ERR("recv failed with error %d\n", WSAGetLastError());
283                 /* FIXME: handle error */
284             }
285 
286             This->iCurrentBufferOffset++;
287         }
288         if (This->iCurrentBufferOffset == This->cbBuffer)
289         {
290             INETXPORT_COMPLETION_FUNCTION fnCompletion = This->fnCompletion;
291             char *pBuffer;
292 
293             This->fnCompletion = NULL;
294             pBuffer = This->pBuffer;
295             This->pBuffer = NULL;
296             fnCompletion((IInternetTransport *)&This->u.vtbl, pBuffer,
297                 This->iCurrentBufferOffset);
298             HeapFree(GetProcessHeap(), 0, pBuffer);
299             return 0;
300         }
301 
302         if (WSAAsyncSelect(This->Socket, hwnd, uMsg, FD_READ) == SOCKET_ERROR)
303         {
304             ERR("WSAAsyncSelect failed with error %d\n", WSAGetLastError());
305             /* FIXME: handle error */
306         }
307         return 0;
308     }
309     else if (uMsg == IX_READLINE)
310     {
311         InternetTransport *This = (InternetTransport *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
312 
313         /* no work to do */
314         if (!This->fnCompletion)
315             return 0;
316 
317         while (This->iCurrentBufferOffset < This->cbBuffer - 1)
318         {
319             fd_set infd;
320 
321             if (recv(This->Socket, &This->pBuffer[This->iCurrentBufferOffset], 1, 0) <= 0)
322             {
323                 if (WSAGetLastError() == WSAEWOULDBLOCK)
324                     break;
325 
326                 ERR("recv failed with error %d\n", WSAGetLastError());
327                 /* FIXME: handle error */
328                 return 0;
329             }
330 
331             if (This->pBuffer[This->iCurrentBufferOffset] == '\n')
332             {
333                 INETXPORT_COMPLETION_FUNCTION fnCompletion = This->fnCompletion;
334                 char *pBuffer;
335 
336                 This->fnCompletion = NULL;
337                 This->pBuffer[This->iCurrentBufferOffset++] = '\0';
338                 pBuffer = This->pBuffer;
339                 This->pBuffer = NULL;
340 
341                 fnCompletion((IInternetTransport *)&This->u.vtbl, pBuffer,
342                     This->iCurrentBufferOffset);
343 
344                 HeapFree(GetProcessHeap(), 0, pBuffer);
345                 return 0;
346             }
347             if (This->pBuffer[This->iCurrentBufferOffset] != '\r')
348                 This->iCurrentBufferOffset++;
349 
350             FD_ZERO(&infd);
351             FD_SET(This->Socket, &infd);
352         }
353         if (This->iCurrentBufferOffset == This->cbBuffer - 1)
354             return 0;
355 
356         if (WSAAsyncSelect(This->Socket, hwnd, uMsg, FD_READ) == SOCKET_ERROR)
357         {
358             ERR("WSAAsyncSelect failed with error %d\n", WSAGetLastError());
359             /* FIXME: handle error */
360         }
361         return 0;
362     }
363     else
364         return DefWindowProcW(hwnd, uMsg, wParam, lParam);
365 }
366 
367 BOOL InternetTransport_RegisterClass(HINSTANCE hInstance)
368 {
369     WNDCLASSW cls;
370     WSADATA wsadata;
371 
372     if (WSAStartup(MAKEWORD(2, 2), &wsadata))
373         return FALSE;
374 
375     memset(&cls, 0, sizeof(cls));
376     cls.hInstance     = hInstance;
377     cls.lpfnWndProc   = InternetTransport_WndProc;
378     cls.lpszClassName = wszClassName;
379 
380     return RegisterClassW(&cls);
381 }
382 
383 void InternetTransport_UnregisterClass(HINSTANCE hInstance)
384 {
385     UnregisterClassW(wszClassName, hInstance);
386     WSACleanup();
387 }
388