Network stack refactor
[novacoin.git] / src / irc.cpp
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2011 The Bitcoin developers
3 // Distributed under the MIT/X11 software license, see the accompanying
4 // file license.txt or http://www.opensource.org/licenses/mit-license.php.
5
6 #include "headers.h"
7 #include "irc.h"
8 #include "net.h"
9 #include "strlcpy.h"
10
11 using namespace std;
12 using namespace boost;
13
14 int nGotIRCAddresses = 0;
15 bool fGotExternalIP = false;
16
17 void ThreadIRCSeed2(void* parg);
18
19
20
21
22 #pragma pack(push, 1)
23 struct ircaddr
24 {
25     struct in_addr ip;
26     short port;
27 };
28 #pragma pack(pop)
29
30 string EncodeAddress(const CService& addr)
31 {
32     struct ircaddr tmp;
33     if (addr.GetInAddr(&tmp.ip))
34     {
35         tmp.port = htons(addr.GetPort());
36
37         vector<unsigned char> vch(UBEGIN(tmp), UEND(tmp));
38         return string("u") + EncodeBase58Check(vch);
39     }
40     return "";
41 }
42
43 bool DecodeAddress(string str, CService& addr)
44 {
45     vector<unsigned char> vch;
46     if (!DecodeBase58Check(str.substr(1), vch))
47         return false;
48
49     struct ircaddr tmp;
50     if (vch.size() != sizeof(tmp))
51         return false;
52     memcpy(&tmp, &vch[0], sizeof(tmp));
53
54     addr = CService(tmp.ip, ntohs(tmp.port));
55     return true;
56 }
57
58
59
60
61
62
63 static bool Send(SOCKET hSocket, const char* pszSend)
64 {
65     if (strstr(pszSend, "PONG") != pszSend)
66         printf("IRC SENDING: %s\n", pszSend);
67     const char* psz = pszSend;
68     const char* pszEnd = psz + strlen(psz);
69     while (psz < pszEnd)
70     {
71         int ret = send(hSocket, psz, pszEnd - psz, MSG_NOSIGNAL);
72         if (ret < 0)
73             return false;
74         psz += ret;
75     }
76     return true;
77 }
78
79 bool RecvLine(SOCKET hSocket, string& strLine)
80 {
81     strLine = "";
82     loop
83     {
84         char c;
85         int nBytes = recv(hSocket, &c, 1, 0);
86         if (nBytes > 0)
87         {
88             if (c == '\n')
89                 continue;
90             if (c == '\r')
91                 return true;
92             strLine += c;
93             if (strLine.size() >= 9000)
94                 return true;
95         }
96         else if (nBytes <= 0)
97         {
98             if (fShutdown)
99                 return false;
100             if (nBytes < 0)
101             {
102                 int nErr = WSAGetLastError();
103                 if (nErr == WSAEMSGSIZE)
104                     continue;
105                 if (nErr == WSAEWOULDBLOCK || nErr == WSAEINTR || nErr == WSAEINPROGRESS)
106                 {
107                     Sleep(10);
108                     continue;
109                 }
110             }
111             if (!strLine.empty())
112                 return true;
113             if (nBytes == 0)
114             {
115                 // socket closed
116                 printf("IRC socket closed\n");
117                 return false;
118             }
119             else
120             {
121                 // socket error
122                 int nErr = WSAGetLastError();
123                 printf("IRC recv failed: %d\n", nErr);
124                 return false;
125             }
126         }
127     }
128 }
129
130 bool RecvLineIRC(SOCKET hSocket, string& strLine)
131 {
132     loop
133     {
134         bool fRet = RecvLine(hSocket, strLine);
135         if (fRet)
136         {
137             if (fShutdown)
138                 return false;
139             vector<string> vWords;
140             ParseString(strLine, ' ', vWords);
141             if (vWords.size() >= 1 && vWords[0] == "PING")
142             {
143                 strLine[1] = 'O';
144                 strLine += '\r';
145                 Send(hSocket, strLine.c_str());
146                 continue;
147             }
148         }
149         return fRet;
150     }
151 }
152
153 int RecvUntil(SOCKET hSocket, const char* psz1, const char* psz2=NULL, const char* psz3=NULL, const char* psz4=NULL)
154 {
155     loop
156     {
157         string strLine;
158         strLine.reserve(10000);
159         if (!RecvLineIRC(hSocket, strLine))
160             return 0;
161         printf("IRC %s\n", strLine.c_str());
162         if (psz1 && strLine.find(psz1) != -1)
163             return 1;
164         if (psz2 && strLine.find(psz2) != -1)
165             return 2;
166         if (psz3 && strLine.find(psz3) != -1)
167             return 3;
168         if (psz4 && strLine.find(psz4) != -1)
169             return 4;
170     }
171 }
172
173 bool Wait(int nSeconds)
174 {
175     if (fShutdown)
176         return false;
177     printf("IRC waiting %d seconds to reconnect\n", nSeconds);
178     for (int i = 0; i < nSeconds; i++)
179     {
180         if (fShutdown)
181             return false;
182         Sleep(1000);
183     }
184     return true;
185 }
186
187 bool RecvCodeLine(SOCKET hSocket, const char* psz1, string& strRet)
188 {
189     strRet.clear();
190     loop
191     {
192         string strLine;
193         if (!RecvLineIRC(hSocket, strLine))
194             return false;
195
196         vector<string> vWords;
197         ParseString(strLine, ' ', vWords);
198         if (vWords.size() < 2)
199             continue;
200
201         if (vWords[1] == psz1)
202         {
203             printf("IRC %s\n", strLine.c_str());
204             strRet = strLine;
205             return true;
206         }
207     }
208 }
209
210 bool GetIPFromIRC(SOCKET hSocket, string strMyName, CNetAddr& ipRet)
211 {
212     Send(hSocket, strprintf("USERHOST %s\r", strMyName.c_str()).c_str());
213
214     string strLine;
215     if (!RecvCodeLine(hSocket, "302", strLine))
216         return false;
217
218     vector<string> vWords;
219     ParseString(strLine, ' ', vWords);
220     if (vWords.size() < 4)
221         return false;
222
223     string str = vWords[3];
224     if (str.rfind("@") == string::npos)
225         return false;
226     string strHost = str.substr(str.rfind("@")+1);
227
228     // Hybrid IRC used by lfnet always returns IP when you userhost yourself,
229     // but in case another IRC is ever used this should work.
230     printf("GetIPFromIRC() got userhost %s\n", strHost.c_str());
231     if (fUseProxy)
232         return false;
233     CNetAddr addr(strHost, true);
234     if (!addr.IsValid())
235         return false;
236     ipRet = addr;
237
238     return true;
239 }
240
241
242
243 void ThreadIRCSeed(void* parg)
244 {
245     IMPLEMENT_RANDOMIZE_STACK(ThreadIRCSeed(parg));
246     try
247     {
248         ThreadIRCSeed2(parg);
249     }
250     catch (std::exception& e) {
251         PrintExceptionContinue(&e, "ThreadIRCSeed()");
252     } catch (...) {
253         PrintExceptionContinue(NULL, "ThreadIRCSeed()");
254     }
255     printf("ThreadIRCSeed exiting\n");
256 }
257
258 void ThreadIRCSeed2(void* parg)
259 {
260     /* Dont advertise on IRC if we don't allow incoming connections */
261     if (mapArgs.count("-connect") || fNoListen)
262         return;
263
264     if (GetBoolArg("-noirc"))
265         return;
266     printf("ThreadIRCSeed started\n");
267     int nErrorWait = 10;
268     int nRetryWait = 10;
269     bool fNameInUse = false;
270
271     while (!fShutdown)
272     {
273         CService addrConnect("92.243.23.21", 6667); // irc.lfnet.org
274
275         CService addrIRC("irc.lfnet.org", 6667, true);
276         if (addrIRC.IsValid())
277             addrConnect = addrIRC;
278
279         SOCKET hSocket;
280         if (!ConnectSocket(addrConnect, hSocket))
281         {
282             printf("IRC connect failed\n");
283             nErrorWait = nErrorWait * 11 / 10;
284             if (Wait(nErrorWait += 60))
285                 continue;
286             else
287                 return;
288         }
289
290         if (!RecvUntil(hSocket, "Found your hostname", "using your IP address instead", "Couldn't look up your hostname", "ignoring hostname"))
291         {
292             closesocket(hSocket);
293             hSocket = INVALID_SOCKET;
294             nErrorWait = nErrorWait * 11 / 10;
295             if (Wait(nErrorWait += 60))
296                 continue;
297             else
298                 return;
299         }
300
301         string strMyName;
302         if (addrLocalHost.IsRoutable() && !fUseProxy && !fNameInUse)
303             strMyName = EncodeAddress(addrLocalHost);
304         else
305             strMyName = strprintf("x%u", GetRand(1000000000));
306
307         Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str());
308         Send(hSocket, strprintf("USER %s 8 * : %s\r", strMyName.c_str(), strMyName.c_str()).c_str());
309
310         int nRet = RecvUntil(hSocket, " 004 ", " 433 ");
311         if (nRet != 1)
312         {
313             closesocket(hSocket);
314             hSocket = INVALID_SOCKET;
315             if (nRet == 2)
316             {
317                 printf("IRC name already in use\n");
318                 fNameInUse = true;
319                 Wait(10);
320                 continue;
321             }
322             nErrorWait = nErrorWait * 11 / 10;
323             if (Wait(nErrorWait += 60))
324                 continue;
325             else
326                 return;
327         }
328         Sleep(500);
329
330         // Get our external IP from the IRC server and re-nick before joining the channel
331         CNetAddr addrFromIRC;
332         if (GetIPFromIRC(hSocket, strMyName, addrFromIRC))
333         {
334             printf("GetIPFromIRC() returned %s\n", addrFromIRC.ToString().c_str());
335             if (!fUseProxy && addrFromIRC.IsRoutable())
336             {
337                 // IRC lets you to re-nick
338                 fGotExternalIP = true;
339                 addrLocalHost.SetIP(addrFromIRC);
340                 strMyName = EncodeAddress(addrLocalHost);
341                 Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str());
342             }
343         }
344         
345         if (fTestNet) {
346             Send(hSocket, "JOIN #bitcoinTEST\r");
347             Send(hSocket, "WHO #bitcoinTEST\r");
348         } else {
349             // randomly join #bitcoin00-#bitcoin99
350             int channel_number = GetRandInt(100);
351             Send(hSocket, strprintf("JOIN #bitcoin%02d\r", channel_number).c_str());
352             Send(hSocket, strprintf("WHO #bitcoin%02d\r", channel_number).c_str());
353         }
354
355         int64 nStart = GetTime();
356         string strLine;
357         strLine.reserve(10000);
358         while (!fShutdown && RecvLineIRC(hSocket, strLine))
359         {
360             if (strLine.empty() || strLine.size() > 900 || strLine[0] != ':')
361                 continue;
362
363             vector<string> vWords;
364             ParseString(strLine, ' ', vWords);
365             if (vWords.size() < 2)
366                 continue;
367
368             char pszName[10000];
369             pszName[0] = '\0';
370
371             if (vWords[1] == "352" && vWords.size() >= 8)
372             {
373                 // index 7 is limited to 16 characters
374                 // could get full length name at index 10, but would be different from join messages
375                 strlcpy(pszName, vWords[7].c_str(), sizeof(pszName));
376                 printf("IRC got who\n");
377             }
378
379             if (vWords[1] == "JOIN" && vWords[0].size() > 1)
380             {
381                 // :username!username@50000007.F000000B.90000002.IP JOIN :#channelname
382                 strlcpy(pszName, vWords[0].c_str() + 1, sizeof(pszName));
383                 if (strchr(pszName, '!'))
384                     *strchr(pszName, '!') = '\0';
385                 printf("IRC got join\n");
386             }
387
388             if (pszName[0] == 'u')
389             {
390                 CAddress addr;
391                 if (DecodeAddress(pszName, addr))
392                 {
393                     addr.nTime = GetAdjustedTime();
394                     if (AddAddress(addr, 51 * 60))
395                         printf("IRC got new address: %s\n", addr.ToString().c_str());
396                     nGotIRCAddresses++;
397                 }
398                 else
399                 {
400                     printf("IRC decode failed\n");
401                 }
402             }
403         }
404         closesocket(hSocket);
405         hSocket = INVALID_SOCKET;
406
407         if (GetTime() - nStart > 20 * 60)
408         {
409             nErrorWait /= 3;
410             nRetryWait /= 3;
411         }
412
413         nRetryWait = nRetryWait * 11 / 10;
414         if (!Wait(nRetryWait += 60))
415             return;
416     }
417 }
418
419
420
421
422
423
424
425
426
427
428 #ifdef TEST
429 int main(int argc, char *argv[])
430 {
431     WSADATA wsadata;
432     if (WSAStartup(MAKEWORD(2,2), &wsadata) != NO_ERROR)
433     {
434         printf("Error at WSAStartup()\n");
435         return false;
436     }
437
438     ThreadIRCSeed(NULL);
439
440     WSACleanup();
441     return 0;
442 }
443 #endif