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